How to access CSS generated content with JavaScript
Asked Answered
O

4

64

I generate the numbering of my headers and figures with CSS's counter and content properties:

img.figure:after {
  counter-increment: figure;
  content: "Fig. " counter(section) "." counter(figure);
}

This (appropriate browser assumed) gives a nice labelling "Fig. 1.1", "Fig. 1.2" and so on following any image.

Question: How can I access this from Javascript? The question is twofold in that I'd like to access either the current value of a certain counter (at a certain DOM node) or the value of the CSS generated content (at a certain DOM node) or, obviously, both information.

Background: I'd like to append to links back-referencing to figures the appropriate number, like this:

<a href="#fig1">see here</h>
------------------------^ " (Fig 1.1)" inserted via JS

As far as I can see, it boils down to this problem: I could access content or counter-increment via getComputedStyle:

var fig_content = window.getComputedStyle(
                    document.getElementById('fig-a'),
                    ':after').content;

However, this is not the live value, but the one declared in the stylesheet. I cannot find any interface to access the real live value. In the case of the counter, there isn't even a real CSS property to query.

Edit: Digging deeper and deeper through the DOM specs, I found the DOM Level 2 Style Counter interface. This seems to a) allow access to the current counter value and b) be implemented in Firefox, at least. However, I have no idea on how to use it. My current approach died tragically after this Firebug output:

// should return a DOM 2 Counter interface implementation...
window.getComputedStyle(fig_a_element, ':after')
      .getPropertyCSSValue("counter-increment")[0]
      .getCounterValue();

[Exception... "Modifications are not allowed for this document" code: "7"
 nsresult: "0x80530007 (NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)"
 location: "http://localhost/countertest.html Line: 71"]

Any idea, how this could be brought to life would be highly appreciated.

Edit 2: Apparently I misinterpreted the Counter object of DOM Level 2 Style. It, too, has no property to return the current counter value. This makes the above approach invalid.

New approach: Is there a possibility to read the content of a pseudo-element via the DOM? That is, can I select the pseudo-element (treeWalker comes to mind) and then get its nodeValue? (If you start to type 'jQuery' now, please reconsider to change that term into 'Sizzle'...)

Oersted answered 16/4, 2010 at 9:1 Comment(2)
getCounterValue doesn't do what it sounds like. It's a clunky type-safe way they've implemented the idea of getting different types of parsed CSS declaration values as structured objects. getCounterValue actually means “get CSS property value, as a Counter object”. The Counter object is only a value type representing the CSS declaration, you can't get the actual counter number from it.Gainor
Yes, after re-reading the spec, apparently the Counter object has no property for the current value. Darn!Oersted
G
24

I cannot find any interface to access the real live value. [of the counter]

Yeah. I don't think there is one. Sorry.

The only thing I can think of would be to go through every element (including its :before/:after pseudo-elements) before the element in the document, looking for counters and adding up how many there are.

Obviously that's hideous. If you're going to try to reproduce the browser's own counter mechanism it would probably be easier (and much more compatible, given IE<=7's lack of counter/content support) to just replace it with your own script-based counters. eg. something along the lines of:

<a href="#prettypicture">this</a>

<div class="counter level=0">...</div>
<img id="prettypicture" class="counter level=1" alt="ooo, pretty"/>
window.onload= function() {
    var counters= Node_getElementsByClassName(document.body, 'counter');
    var indices= [];
    for (var counteri= 0; counteri<counters.length; counteri++) {
        var counter= counters[counteri];

        var level= Element_getClassArgument(counter, 'level');
        while (indices.length<=level)
            indices.push(0);
        indices[level]++;
        indices= indices.slice(level+1);
        var text= document.createTextNode('Figure '+indices.join('.'));
        counter.parentNode.insertBefore(text, counter.nextSibling);

        if (counter.id!=='') {
            for (var linki= document.links.length; linki-->0;) {
                var link= document.links[i];
                if (
                    link.hostname===location.hostname && link.pathname===location.pathname &&
                    link.search===location.search && link.hash==='#'+counter.id
                ) {
                    var text= document.createTextNode('('+indices.join('.')+')');
                    link.parentNode.insertBefore(text, link.nextSibling);
                }
            }
        }
    }
};
Gainor answered 16/4, 2010 at 10:26 Comment(1)
It seems, that in theory there is an interface to the counter value. I think I updated the question after you answered. Otherwise, I agree that I don't want to loop through an arbitrary amount of elements just to find their content or counter-increment CSS property.Oersted
K
15

read this:

Generated content does not alter the document tree. In particular, it is not fed back to the document language processor (e.g., for reparsing).


Content specified in a stylesheet does not become part of the DOM.

so for this reason the getComputedStyle will not work in this case; i think the only way, is to perform a classic loop through as someone has described below!

Kettering answered 24/5, 2010 at 23:18 Comment(4)
'Never' is a strong word. Apparently, there was an old, but insufficient API to style values (without access to the counters), but as I was told, there a re thoughts on a new API to access styling info from within the DOM: lists.w3.org/Archives/Public/www-style/2010Apr/0136.html . I don't lose the hope, that one day it will work. As for now, I think the self-written loop is indeed the only possibility.Oersted
Sorry bro! but IMHO never is not strong in this case simply cause you are trying to access to something that is not been created at DOM level! ;-)Kettering
Why do you think it's necessary for the generated content to be part of the DOM in order to use getComputedStyle? The only limitation of getComputedStyle could be that the computed value of content is the specified value, then we would need the used or the actual values instead of the computed one. The latest spec draft does not say what the computed value should be.Olimpiaolin
Did this ever change?Scarlett
B
2

I would port your css to Javascript, this enables you to get the figure caption and also you get greater browser coverage. Using jQuery you'd do something like this:

$(function() {
  var section = 0;
  $(".section").each(function() {
    section++;
    var figure = 0;
    $(this).find("img.figure").each(function() {
      figure++;
      var s = "Fig. " + section + "." + figure;
      $(this).attr({alt:s}).after(s);
    });
  });
});

Then you could do:

<div class="section">blabla <img id="foo" src="http://www.example.com/foo.jpg"></div>
<p>Lorem ipsum dolor sit amet <a href="#foo" class="figurereference">see here</a></p>

<script type="text/javascript">
  $(function() {
    $("a.figurereference").each(function() {
      var selector = $(this).attr("href");
      var $img = $(selector);
      var s = $(this).text() + " (" + $img.attr("alt") + ")";
      $(this).text(s);
    });
  });
</script>

Though I agree that doing this using CSS would be very neat.

Berezniki answered 18/5, 2010 at 9:13 Comment(0)
E
-1

I cannot find any interface to access the real live value.

If you can't get the value from window.getComputedStyle, it seems that it would be impossible.

More importantly, while it can do it, I think this might be abusing CSS since you're breaking the barrier between content and presentation at this point.

Check out this related question What are good uses of css “Content” property?

Expansion answered 16/4, 2010 at 10:17 Comment(3)
I don't think, that I abuse CSS in any way. If I did, wherefore would the Generated Content chapter in the CSS 2.1 spec be? IMHO, it's a matter of style, how I label my figure. It's no essential part of the information of the figure itself, that it's preceeded by the string "Fig. 1.1:". I could labelling it "1.1" or "Figure 1" or anything else, which is just a kind of extended bullet point (and I think, you agree, that that is really just style).Oersted
I would agree with the bullet analogy if it is only in the context of a list and if it is an indication of a new list item; otherwise, this is a caption and captions are content. Also, regardless of what a spec says, it still violates the idea of content and presentation separation. However, if this isn't a concern, who am I to stop you ;)Expansion
Actually, I simplified my above examples exactly by omitting the caption. In the real case, the "Fig 1.1" prefixes the actual figure caption (like <p><img/><span class="caption">This is an image of Foo</span></p>). Otherwise, I agree with you, that CSS generated content can be abused to inject semantically meaningful content, which is IMHO a bad thing to do.Oersted

© 2022 - 2024 — McMap. All rights reserved.