Describe more using CSS!
Use CSS to describe menial tasks in JavaScript like wrapping elements for styling, moving content around, numbering things, clickable footnotes, and more!
// Use jQuery selector Extensions
.example:has(>.title) { border: 1px dotted; }
.example:lt(3) { background-color: red; }
// Wrap Elements just for styling purposes
.example:outside { border: 1px solid red; }
.example:outside:before { content: 'Example: '; }
// Floating, clickable Footnotes
.footnote:footnote-call { x-tag-name: 'a'; }
// Advanced Attribute Lookup
.note[title] > .title { content: x-parent(attr(title)); }
// Number links to other elements
a[href] {
content: 'See Chap ' target-counter(attr(href), chapter)
': ' target-text(attr(href), content());
}
// Customize Link text based on what the link target is
a[href] {
content: x-target-is(attr(href), 'table') 'See Table';
content: x-target-is(attr(href), '.note') 'See Note';
}
// Move content in the DOM
.definition { move-to: glossary-bucket; }
.glossary-area { content: pending(glossary-bucket); }
// Set a string ... and then use it
h1 { string-set: chapter-title content(); }
.chap-end { content: '[End of ' string(chapter-title) ']'; }
CSS is constantly changing and there is a lack of tools for experimenting with new ways of using it.
There are some great Working Drafts that describe extensions to CSS (Generated Content for Paged Media and CSS3 Generated and Replaced Content Module), features often implemented using ad-hoc JavaScript.
Below are some interactive examples showing how powerful CSS can be at describing these common operations.
For a full list of CSS selectors, rules, and functions see the GitHub README
Running in a browser is the easiest way to get started and good for testing out the CSS.
Include the stylesheet with the rel
set to "stylesheet/css-polyfills"
:
<link rel="stylesheet/css-polyfills" type="text/css" href="styles.css" />
Then use the dcss-polyfills.js
built in the /dist/
directory, and include it in your page, like so:
<script src="dist/css-polyfills.js"></script>
See examples/browser.html for an example.
Moving content in the DOM is simple using the move-to: bucket-name;
and content: pending(bucket-name);
rules defined in CSS3 Generated and Replaced Content Module.
// This element will be moved into the glossary-bucket...
.def-a { move-to: bucket-a; }
.def-b { move-to: bucket-b; }
// ... and dumped out into this area in the order added.
.area-a { content: pending(bucket-a); }
.area-b { content: pending(bucket-b); }
// Also, styling occurs **before** elements are moved so ...
.before div { background-color: lightgreen; }
Looking up text inside some other element is simple using the content: target-text(attr(href));
and content: target-counter(attr(href), chapter-coutner);
rules defined in CSS Generated Content for Paged Media Module.
// Just set a counter so we can look it up later
h3 { counter-increment: chap; }
h3:before { content: 'Ch ' counter(chap) ': '; }
// Look up the text on the target
.xref {
content: 'See ' target-text(attr(href), content(contents));
}
// Look up the counter on the target
.xref-counter {
content: 'See Chapter ' target-counter(attr(href), chap);
}
It may be useful to add elements into the HTML (for styling). CSS2 has the :before
and :after
selectors for this reason.
The CSS3 Content Module is like :before
on steroids! You can nest the pseudo selectors to add and style arbitrarily large elements. It also introduces the :outside
pseudoselector which wraps an element and adds additional styling.
The example below builds on the previous example of using counters and target-text
to allow styling individual parts of the injected "Ch #: " text.
h3:before { content: 'Ch ' counter(chap) ': '; }
// The previous rule is valid CSS2 and creates the following DOM:
// [ [Ch 2:] Chapter Title]
// Note: There are only 2 elements we can style
h3 { counter-increment: chap; }
h3:before:before { content: '[Ch ]'; }
h3:before { content: '[' counter(chap) ']'; }
h3:before:after { content: '[: ]'; }
h3:outside:before { content: '(chapter starts here)'; }
// Instead, the previous styles create the following
// (providing 7 elements that can be styled):
// [
// [Chapter starts here]
// [
// [ [Ch] [2] [: ] ]
// Chapter Title
// ]
// ]
Sometimes it is useful to set a string and then use it later. An example would be having the current chapter on each page of a book.
Fortunately CSS Generated Content for Paged Media defines the string-set: string-name value;
and content: string(string-name);
rules.
An example is shown below:
// Set a string somewhere...
h3 { string-set: chapter-title content(); }
// ... And then use it!
.chap-end { content: '[End of ' string(chapter-title) ']'; }
Sometimes it is useful to change the text of a link based on what it points to. For example, a link to a figure may say "see Figure 4.3" and a link to another chapter might say "See Section 7.2: Trapezoids".
Changing the text of a link can be accomplished using x-target-is(id, '.title')
which works like a switch statement in many programming languages.
An example is shown below:
a[href] {
// Use x-target-is as a switch for which link text to use
content: x-target-is(attr(href), 'figure') 'See Figure';
// Link to a section WITHOUT a title
content: x-target-is(attr(href), 'section:not(:has(>.title))')
'See ' target-text(attr(href), content(before));
// Link to a section **with** a title
content: x-target-is(attr(href), 'section:has(>.title)')
'See ' target-text(attr(href), content(before))
target-text(attr(href), content('> .title'));
}
// Some uninteresting formatting just for the demo
section { counter-increment: section; }
section::before { content: counter(section) ' '; }
Footnotes that move to the bottom of a page and leave a little numbered link are described in Generated Content for Paged Media: Footnotes
They can be implemented using the pseudoselector :footnote-call
and the extension rules x-tag-name:
, x-attr:
, and x-ensure-id:
.
Here is an example implementation of footnotes.
.footnote {
// Ensure the footnote has an `id` (so we can link to it)
x-ensure-id: 'id';
// Move it to the next `footnote-area` (page end)
move-to: footnote-area;
counter-increment: footnote;
display: block; // Plain CSS, nothing interesting
}
// The content that is left behind after the move-to
.footnote:footnote-call {
// Make the stub that is left behind a link...
x-tag-name: 'a';
// ... whose href points to the footnote.
x-attr: href '#' attr(id);
content: '[###]';
content: '[' target-counter(attr(href), footnote) ']';
}
//.footnote:footnote-marker,
.footnote:before {
content: counter(footnote) ': ';
}
// Area where the footnotes will be collected
.footnotes-area {
content: pending(footnote-area);
//counter-reset: footnote;
}
Sizzle has several useful extensions to CSS selectors that allow us to target elements better. Now, you can use them directly in CSS files!
Some examples include: :has()
for looking inside an element and :lt()
for matching elements less than a specific index.
Try using them with the demo below!
.example:has(>.title) {
background-color: pink;
}
.count:lt(3) {
background-color: lightblue;
}
Continuing with the Move example, it may be nice to sort the elements being moved like in a glossary.
Adding these extensions is pretty straightforward. Here is the definition of x-sort(bucket)
.
'x-sort': (env, pendingElsNode) -> pendingEls = pendingElsNode.value sorted = pendingEls.sort (a, b) -> a = a.text() b = b.text() return -1 if (a < b) return 1 return sorted
You can use it in a demo below. Note that the items are sorted alphabetically.
// This element will be moved into the glossary-bucket...
.def {
move-to: glossary-bucket;
}
// ... and dumped out into this area in the order added.
.glossary-area {
content: x-sort(pending(glossary-bucket));
}