Describe more using CSS!
Use CSS to describe menial tasks in JavaScript likewrapping 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 elementsa[href] { content: 'See Chap ' target-counter(attr(href), chapter) ': ' target-text(attr(href), content());}// Customize Link text based on what the link target isa[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 ith1 { 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 andCSS3 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 therel
set to"stylesheet/css-polyfills"
:
<link rel="stylesheet/css-polyfills" type="text/css" href="styles.css" />
Then use thedcss-polyfills.js
built in the/dist/
directory, and include it in your page, like so:
<script src="dist/css-polyfills.js"></script>
Seeexamples/browser.html for an example.
Moving content in the DOM is simple using themove-to: bucket-name;
andcontent: pending(bucket-name);
rules defined inCSS3 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 thecontent: target-text(attr(href));
andcontent: target-counter(attr(href), chapter-coutner);
rules defined inCSS Generated Content for Paged Media Module.
// Just set a counter so we can look it up laterh3 { 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.
TheCSS3 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 andtarget-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 styleh3 { 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.
FortunatelyCSS Generated Content for Paged Media defines thestring-set: string-name value;
andcontent: 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 usingx-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 demosection { 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 inGenerated Content for Paged Media: Footnotes
They can be implemented using the pseudoselector:footnote-call
and the extension rulesx-tag-name:
,x-attr:
, andx-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 usefulextensions 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 ofx-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));}