Toon posts:

CSS counter benaderen met JS

Pagina: 1
Acties:

Verwijderd

Topicstarter
Ik heb een document met figuren erin, deze worden genummerd dmv css counters, werkt netjes.

Nu wil ik in de tekst refereren naar figuurnummers. Uiteraard weet ik de nummering niet van te voren (deze kan veranderen), dus ik wil eigenlijk refereren naar een id en dan met javascript het juiste nummer van een element te voorschijn toveren

in het kort: hoe krijg ik met js de css nummering van een element (of de inhoud van het :before pseudoelement)

style properties van pseudoelementen zijn wel accessible (iig uit te lezen) met window.getComputedStyle(el,':before').
content zit daar wel in als prop, maar bevat nooit wat.

  • BtM909
  • Registratie: Juni 2000
  • Niet online

BtM909

Watch out Guys...

Ik heb hier ook mee lopen stoeien en ben toen weer van CSS counters afgestapt :/

Ik zal ff kijken wat voor oplossing ik toen gekozen heb.

Ace of Base vs Charli XCX - All That She Boom Claps (RMT) | Clean Bandit vs Galantis - I'd Rather Be You (RMT)
You've moved up on my notch-list. You have 1 notch
I have a black belt in Kung Flu.


Verwijderd

Topicstarter
wat ik ondertussen wel heb is met js de hele dom doorlopen en ondertussen kijken naar de style van elementen om zo zelf een array met countertjes op te bouwen

dat werkt, maar nu zou ik ook wel eens die waarde aan een element hangen (textnode aan het begin en een extra property ofzo), maar in de css kijken werkt niet aangezien de content property leeg is, dus weet ik niet voor welk element ik in de css content met een counter heb gedefinieerd.

Verwijderd

Ik ben dit weleens tegen gekomen in de CSS3 specs:
http://www.w3.org/TR/2006...0060919/#cross-references
Cross-reference are generated by styling the source anchor of a link in a special way. Instead of styling the source anchor by (say) showing it underlined in blue, the style sheet can (say) specify that the source anchor should be presented with a page number. For example, the string " (see page 72)" could be added to the link.

Here is the CSS code to achieve this on common HTML markup:
Cascading Stylesheet:
1
a::after { content: "(see page " target-counter(attr(href), page, decimal) ")" }


De kans is erg klein dat dit voor jou werkt maar ik wilde het toch even vermelden :)

Verwijderd

Topicstarter
die ken ik ja, maar dat werkt dus helaas ook nergens

wat ik nu doe is het volgende
- onload alle elementen langslopen en kijken naar counter-reset en counter-increment style properties (de pseudoelementen sla ik over, maar eventueel zou je die erbij kunnen pakken)
- met behulp van die waarden een named array maken en die opslaan in elk element (alle subelementen van een hoofdstuk weten zo ook in welk hoofdstuk ze zitten)
- de css tellers gewoon hun werk laten doen, dit loopt dus compleet los van elkaar

vervolgens heb ik anchor elementen in de code a la:
HTML:
1
<a rel="figure chapter.figure" href="#some_figure_id"></a>

dat rel attribuut is dus wat speciaal, ik splits 'm op spaties, het eerste deel wordt een soort caption, het tweede deel is een expression die refereert naar de counters. Voor elk woorddeel (split(/\b/)) kijk ik of die key in de counter array van het targetelement staat, zoja, vervang ik het door de counter, zoniet, dan laat ik het woorddeel staan. De punt blijft in dit geval dus staan. Het resultaat plak ik als textnode in het a element.

Wat vinden jullie ervan? misschien een ander attribuut gebruiken? kan het netter? slimmer? meer richting css3 syntax wat blues schetst?

edit: code is alleen getest op firefox, enige wat voor mij effe van belang is, maar het idee moet overal wel werken als de css rules voor counterReset en counterIncrement tenminste uit te lezen zijn (gok: niet het geval in IE)

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function applyCounters() {
    var counters=[];
    var all = document.getElementsByTagName('*');
    for (var i=0; i<all.length; i++) {
        s = getComputedStyle(all[i],'');    
        if (s.counterReset!='none') {
            var cr = s.counterReset.split(' ');
            for (var j=0; j<cr.length; j++) {
                counters[cr[j]] = 1*cr[++j];
            }
        }
        if (s.counterIncrement!='none') {
            var ci = s.counterIncrement.split(' ');
            for (var j=0; j<ci.length; j++) {
                counters[ci[j]] += 1*ci[++j];
            }
        }
        all[i].counter = counters.copy();
    }
}

function linkCounters() {
    var as = document.getElementsByTagName('a');
    var types='equation|figure|table';
    for (var i=0; i<as.length; i++) {
        if (as[i].getAttribute('rel') && types.indexOf(as[i].getAttribute('rel').split(' ')[0])!==-1) {
            var capt = as[i].rel.split(' ')[0];
            var expr = as[i].rel.split(' ')[1].split(/\b/gi);
            var target = document.getElementById(as[i].getAttribute('href').substring(1));
            var val = '';
            for (var j=0; j<expr.length; j++) val+=(target.counter[expr[j]])?target.counter[expr[j]]:expr[j];
            as[i].appendChild(document.createTextNode(capt+' '+val));
        }
    }
}


edit: iets aangepast, ik haal nu eerst de textnodes die al in de a bestaan weg, zo kan je bijvoorbeeld standaard linken naar "the figure below" en als deze hele reutemeteut werkt wordt dat dan vervangen door "figure 3.2" ofzo, beetje meer a la replaced content dus.

[ Voor 40% gewijzigd door Verwijderd op 10-10-2006 15:31 ]


Verwijderd

Topicstarter
update
de bovenstaande methode had m.i. wat nadelen:
• verlies sematische waarde van het rel attribuut
• extra rommel in de html source
• een nieuwe syntax
• weinig algemeen

dus ik heb even wat nieuws gebakken, namelijk die tweede functie vervangen door een functie die iha content toevoegd aan een element:
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function attr(el,args) {
    return (r=el.getAttribute(args))?'"'+r+'"':'';
}
function counter(el,args) {
    return (r=el.counter[args])?'"'+r+'"':'';
}
function target_counter(el,args) {
    args = args.split(',');
    var id = args[0].replace(/[\s^]*"|"[\s$]/gi,'').substring(1);
    return (e=document.getElementById(id))?'"'+e.counter[args[1]]+'"':'';
}
function target_string(el,args) {
    args = args.split(',');
    var id = args[0].replace(/[\s^]*"|"[\s$]/gi,'').substring(1);
    if ((e=document.getElementById(id))==null) return '';
    if (!args[1]) args[1]='content';
    switch(args[1]) {
        case 'content': return '"'+e.textContent+'"';
        case 'first-letter': return '"'+e.textContent.substring(0,1)+'"';
    }
    
}

function applyContent(nodeList,contentString,pseudoElement) {
    for (var i=0; i<nodeList.length; i++) {
        c = contentString;
        //match every function that has no other functions nested
        fs = c.match(/[\w-]*\([^\(]*?\)/gi);
        while(fs) { 
            for (var j=0; j<fs.length; j++) {
                //extract function name and arguments
                var pcs=fs[j].split(/[\(\)]/gi);
                //tidy up function name for js compatibility
                var f = pcs[0].replace('-','_');
                //get the return value of the function if it exists
                var rep = (window[f] && typeof window[f]=='function')?window[f](nodeList[i],pcs[1]):'';
                //change the content string with return values
                c = c.replace(fs[j],rep);
            }
            fs = c.match(/[\w-]*\([^\(]*?\)/gi);
        }
        //only keep string parts (bounded by ") and glue pieces
        c = c.match(/".*?"/gi).join('').replace(/"/g,'');

        //insert a new textnode
        cn = document.createTextNode(c);
        if (!pseudoElement) {
            while(nodeList[i].hasChildNodes()) nodeList[i].removeChild(nodeList[i].firstChild);
            nodeList[i].appendChild(cn);
        }
        if (pseudoElement=='after') nodeList[i].appendChild(cn);
        if (pseudoElement=='before') nodeList[i].insertBefore(cn,nodeList[i].firstChild);
    }
}

deze functie wil een nodelist, ik laat even buiten beschouwing hoe je daar aan komt, maar wellicht zwerft er ergens een getElementsByCSSSelector() functie rond. Vervolgens een content string a la w3c spec, en een optionele component die aangeeft of die content before of after het element moet. Laat je die weg dan wordt de content vervangen.

CSS functies haalt ie dus uit de content string en worden uitgevoerd als js functies. Dit is dus lekker uitbreidbaar (denk aan pending()) Die functies krijgen het refererende element mee en een string (args) met daarin alles wat tussen functie haken in de content string stond.

Het is niet helemaal foolproof nu, regexen kunnen vast wat strakker (kijkt naar regexhelden hier). Een functieconstructie in een string zou bijvoorbeeld gewoon letterlijk overgenomen moeten worden, maar dat wordt dus nu ook omgezet.

Het is misschien leuk dit uit te bouwen met meer functionaliteiten

edit: in die counter functie heb ik ook nog een probleem met nested counters als in http://www.w3.org/TR/2003/WD-css3-content-20030514/#nested

Verwijderd

Topicstarter
Even een update en een vraag

Ik heb nu het stuk wat de contentString parsed in een aparte functie gezet, zodat dit vaker gebruikt kan worden. Hiernaast heb ik ook een functie geschreven om make-element te vervangen, werkt verder ongeveer hetzelfde, maar kopieert de inhoud van een element in een prototype container (zie css3 spec)

Nu heb ik dus ook mechanismen om een ToC en index te bouwen a la css3 geimplementeerd, maar wat is nu de CSS way om je ToC entries te linken naar het element dat ze gemaakt heeft?
Pagina: 1