8

I need to find all block elements in a given node. Block elements are not just elements that have display:block in the CSS, but also default block elements like div and p.

I know I can just get computed style of the element and check for the display property, however, my code will execute in a long loop and getting computed styles flushes reflow stack every time, so it will be very expansive.

I'm looking for some trick to do this without getComputedStyle.

Edit

Here's my current code that I would like to improve:

var isBlockOrLineBreak = function(node)
{
    if (!node) {
        return false;
    }
    var nodeType = node.nodeType;
    return nodeType == 1 && (!inlineDisplayRegex.test(getComputedStyleProperty(node, "display")) || node.tagName === "BR")
        || nodeType == 9 || nodeType == 11;
};

Another edit

jQuery's .css calls getComputedStyle under the hood. So that's not what I'm looking for.

My solution

Thanks everyone for suggestions. Unfortunately, none of them matched what I was looking for. After a lot of digging through documentation I realized that there's no real way to do this without getComputedStyle. However, I came up with the code that should avoid getComputedStyle as much as humanly possible. Here's the code:

$.extend($.expr[':'], {
    block: function(a) {
        var tagNames = {
            "ADDRESS": true,"BLOCKQUOTE": true,"CENTER": true,"DIR": true,"DIV": true,
            "DL": true,"FIELDSET": true,"FORM": true,"H1": true,"H2": true,"H3": true,
            "H4": true,"H5": true,"H6": true,"HR": true,"ISINDEX": true,"MENU": true,
            "NOFRAMES": true,"NOSCRIPT": true,"OL": true,"P": true,"PRE": true,"TABLE": true,
            "UL": true,"DD": true,"DT": true,"FRAMESET": true,"LI": true,"TBODY": true,
            "TD": true,"TFOOT": true,"TH": true,"THEAD": true,"TR": true
        };
        return $(a).is(function() {
            if (tagNames[this.tagName.toUpperCase()]) {
                if (this.style.display === "block")
                {
                    return true;
                }
                if (this.style.display !== "" || this.style.float !== "")
                {
                    return false;
                }
                else {
                    return $(this).css("display") === "block";
                }
            }
            else {
                if (this.style.display === "block") {
                    return
                }
                else {
                    return $(this).css("display") === "block";
                }
            }
        });
    }
});

Usage of this code is very simple just do $(":block") or $("form :block"). This will avoid using .css property in a lot of cases, and only fallback to it as a last resort.

Starx's answer was what gave me the idea to do this, so I'm going to mark his message as an answer.

Ilya Volodin
  • 10,929
  • 2
  • 45
  • 48
  • I'm not tying to avoid the looping, just trying to avoid calling getComputedStyle in a loop. – Ilya Volodin Mar 16 '12 at 03:38
  • how would you know if you don't get the computed style? and it would be better if you included your code also. – Joseph Mar 16 '12 at 03:48
  • Well, that's sort of why I'm asking the question. I'm not sure how to do this without getComputedStyle, but maybe somebody can come up with a smart trick that I didn't think about. – Ilya Volodin Mar 16 '12 at 03:58
  • 1
    See the 2nd response in this post: http://stackoverflow.com/questions/4220478/get-all-dom-block-elements-for-selected-texts - uses regex matching on the tag name itself. – TheOx Mar 16 '12 at 04:05

3 Answers3

4

For the answer to this problem, we take into account the universal CSS selector and the jQuery .filter() function:

$("*").filter(function(index) {
    return $(this).css("display") == 'block';
});

This code looks at all elements it can find, and it returns a list of elements if they pass a filter. The element passes a filter if the filter function returns true for that element. In this case, the filter tests the display property of each found element and tests it against the desired value.

Now, you also mentioned that you want to find p and div elements. Luckily, we also have a way to find these in the filter function. Using jQuery's prop function, we can return a property of an element. In this case, we are interested in the tagName property of the DOM elements being filtered. Combining this feature with the above filter, we get:

$("*").filter(function(index) {
    var $this = $(this);
    var tagName = $this.prop("tagName").toLowerCase();
    return $this.css("display") == 'block' || tagName == 'p' || tagName == 'div';
});

Notice how we set the tagName variable to lowercase, because we cannot expect a certain case for the tagName property (correct me if I'm wrong).

fruchtose
  • 1,270
  • 2
  • 13
  • 16
  • Thanks. This seems to work. I just want to make sure that .css doesn't use getComputedStyle under the hood. I'll go through css code tomorrow. And now I'm really curios as to how jQuery manages to do this without getComputedStyle. – Ilya Volodin Mar 16 '12 at 04:17
  • @IlyaVolodin I edited my answer to also filter by `p` and `div` tags, if you are interested. I believe this should fully answer your question. – fruchtose Mar 16 '12 at 04:18
  • I just checked and unfortunately, jQuery's .css does call getComputedStyle under the hood. So this is more or less the same thing I'm doing in my example, just shorter. – Ilya Volodin Mar 16 '12 at 04:36
  • If you are not going to use `.css()` then you will be hard-pressed to find a JavaScript solution that does not involve decorating elements with a custom class, which you would then search for. In fact, I challenge you to find *any* solution that does not use `getComputedStyle`. jQuery is as close as we get to a cross-browser JavaScript extended library. And if jQuery uses it, good luck finding something that does not. You should look elsewhere to optimize your code if that is truly necessary. – fruchtose Mar 16 '12 at 04:47
2

The best way I see is to

  • assign a common class to all the not-native block element and
  • using jQuery's mulitple-selector.

Then we can do it as simple as this this

CSS:

.block { display: block; }

jQuery:

var blockelements = $("div, p, table, ..., .block"); 
                                   // ^ represents other block tags

If you want to include all the block elements. Here is a link

Community
  • 1
  • 1
Starx
  • 77,474
  • 47
  • 185
  • 261
0

maybe this helps.

$('*').each( function(){
    if ($(this).css("display") === "block")
        $(this).css("background", "yellow") ;   
});

jsfiddle

sbagdat
  • 830
  • 11
  • 25
  • Unfortunately, your example doesn't work. It highlights labels and dropdown. Both of them are inline elements. And even if you add style="display:inline" on a div, it's still has yellow background. Not really sure why though. – Ilya Volodin Mar 16 '12 at 04:12