It is a common predicament: You have an HTML fragment with a table, list or dictionary in it, generated from some data, and you have to render it in such a way that the data is easy to read, and the information presented in such a way as to prevent misunderstandings: However, you can’t alter the HTML source in order to add CSS classes to individual elements. We’ll take a couple of practical examples to show how you can solve this sort of problem.
The flurry of browser upgrades has had the result that a lot of useful CSS2 and CSS3 has been recently implemented. As well as helping the creation of the more esoteric mobile-applications and games, it is the extended ways of addressing individual DOM elements that is making the basic layout of text and tables a lot easier. This article aims to illustrate practical ways of doing this
With CSS3, you can more easily apply styles to elements of the DOM based on their location within the DOM. This solves many problems with the basic layout of text. You may be faced with the requirement that the first paragraph in a DIV section may be formatted differently, you may want to tuck a bulleted list in under the paragraph that precedes it. You may want code to have minimum paragraph spacing when it is preceded by another code paragraph. In the old days, you would need some work to do by hand in order to to tidy up text to make it conform to even the most rudimentary typesetting conventions.
CSS previously gave you some addressing methods that could be used. You could apply styles based on the ID of the DOM Element, or could apply it to all the children of a particular element, or element class, or to all the children of a particular TagType (e.g. A, DIV, P ). CSS2 and CSS3 give you a lot more besides. You can apply a style to the ‘N’th child, or to every other child, whether they be even or odd in the sequence. ( 2, 4, 6, 8, 10 or 1, 3, 5, 7, 9¦) This is just the start. You can match the first element and no others, or the first few. You can specify this in inverse order, from the last element. If you want to disregard the parentage of the elements that you wish to attach a style to, then you can do so, and just specify elements by their sequence irrespective of their place in the hierarchy. At last, all the current browsers support this. Even Internet Explorer 9 will do it! The full list of these CSS addressing methods is contained in Simple-Talk’s XPath, CSS, DOM, Selenium. Rosetta Stone and Cookbook. wallchart here, together with the equivalent XPath and javascript syntax.
To illustrate how this simplifies perfectly conventional formatting, we’ll take a very simple example. You have a list and you’d like to enhance it so the background colour on alternating rows is different.
- Solomon Grundy,
- Born on a Monday,
- Christened on Tuesday,
- Married on Wednesday,
- Took ill on Thursday,
- Grew worse on Friday,
- Died on Saturday,
- Buried on Sunday.
- This is the end of Solomon Grundy.
- Solomon Grundy,
- Born on a Monday,
- Christened on Tuesday,
- Married on Wednesday,
- Took ill on Thursday,
- Grew worse on Friday,
- Died on Saturday,
- Buried on Sunday.
- This is the end of Solomon Grundy.
What do you mean, they look the same? We’re talking about CSS3, so you will need an up to date version of any of the browsers. Even Internet Explorer should show the stripes in the right-hand list unless IE has gone quietly into ‘compatibility mode’. (look for the grey border around the browser window). See here for a fix. Hopefully, with an up-to-date browser, you’ll see how we’re using the CSS3 syntax for applying different styles to alternating elements
Both the CSS and the HTML are very simple. The CSS has something that looks a lot like a CSS2 pseudo-class, and in fact extends the idea. It is called a structural pseudo-class.
1 2 3 4 5 6 7 8 |
<style type="text/css"> ol.stripedlist{ list-style:none; } ol.stripedlist li:nth-child(2n-1){ background-color: #fafad2; } </style> |
And the HTML has no formatting beyond a couple of DIVs to position the two lists side by side.
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 |
<div style="float:left; width:300px"> <ol><li>Solomon Grundy,</li> <li>Born on a Monday,</li> <li>Christened on Tuesday,</li> <li>Married on Wednesday,</li> <li>Took ill on Thursday,</li> <li>Grew worse on Friday,</li> <li>Died on Saturday,</li> <li>Buried on Sunday.</li> <li>This is the end of Solomon Grundy.</li> </ol> </div> <div style="width:300px"> <ol class="stripedlist"> <li>Solomon Grundy,</li> <li>Born on a Monday,</li> <li>Christened on Tuesday,</li> <li>Married on Wednesday,</li> <li>Took ill on Thursday,</li> <li>Grew worse on Friday,</li> <li>Died on Saturday,</li> <li>Buried on Sunday.</li> <li>This is the end of Solomon Grundy.</li> </ol> </div> |
For a simple example like this, it is easier, and more backward-compatible, to simply assign a class to alternating elements to achieve the effect. However, you often can’t do this, and where you are receiving an XHTML fragment from a source that knows only about the data rather than your requirements to make it easy on the eye, then it obviates the task of altering XHTML, which can get messy.
When content is generated from a database, it is very quick to supply it to the application as an XHTML fragment, using the XML extensions to SQL syntax. The actual data structure you use depends on the structure of the data. Most commonly, it will be a table, but I’ve used lists, Dictionary lists, and nested DIVs as well as tables. Here is a quick example of a table generated from AdventureWorks.
DECLARE @query NVARCHAR(MAX)
SET @query = ‘<table>
<caption>AdventureWorks Employees</caption>
<tr><th>Employee Name</th><th>Phone</th><th>Email</th></tr>’
+ REPLACE(CAST((SELECT TOP 20 –purely for demonstration purposes
td = COALESCE(Title + ‘ ‘,”)
+ COALESCE(firstname + ‘ ‘,”)
+ COALESCE(Middlename+’ ‘,”)
+ Lastname, ”,
td = Phone,”,
td = EmailAddress
FROM
person.contact
FOR XML PATH(‘tr’),TYPE) AS NVARCHAR(MAX)),
‘</tr><tr>’,'</tr>
<tr>’) + ‘</table>
‘
SELECT @Query
Tables are much easier to read if one can apply subtle shades to the backgrounds or borders to delineate columns and rows. You can even do rather nice ‘warp and weft’ effects to make it easier for the eye to follow down a column or along a row. Additionally, any Excel user will know how important the column and row headers are to help people understand data in tables. If you generate an XHTML table from SQL Server, you don’t get an option to apply different classes to different columns or rows. Why should you need to do so, when the way that a table is displayed is none of the business of the database.
We can leave the HTML structure as it is without assigning any classes. We just create a style sheet. Here is an example. I’m using bitmaps instead of flat background colors just because I prefer the slightly livelier effect.
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 |
/* do the basic style for the entire table */ table { border-collapse: collapse; border: 1px solid #3399FF; font: 10pt Verdana, Geneva, Arial, Helvetica, sans-serif; color: black; } /*attach the styles to the caption of the table */ table caption { font-weight: bold; background-image: url(fieldBack.bmp); } /*give every cell the same style of border */ table td, table th, table caption { border: 1px solid #eaeaea; } /* make the first column (not header) blue */ table td:nth-child(1){ color: #00016c; } /* apply styles to the third column only */ table td:nth-child(3){ font-variant: small-caps; } /* apply styles to the odd headers */ table th:nth-child(odd) { background-image: url(headingback.bmp); } /* apply styles to the even headers */ table tr th:nth-child(even) { background-image: url(headingCrossingback.bmp); } /* apply styles to the even rows */ table tr:nth-child(even) { background-image: url(fieldBack.bmp); } /* apply styles to the odd colums of even rows */ table tr:nth-child(even) td:nth-child(odd){ background-image: url(fieldBackAlt.bmp); } /* apply styles to the odd rows */ table tr:nth-child(odd){ background-image: url(fieldBack.bmp); } /* apply styles to the even colums of odd rows */ table tr:nth-child(odd) td:nth-child(even){ background-image: url(fieldBackAlt.bmp); } |
And the HTML source of the table (truncated) looks like this
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<table> <caption>AdventureWorks Customers</caption> <tr><th>Employee Name</th><th>Phone</th><th>Email</th></tr> <tr><td>Mr. Gustavo Achong</td><td>398-555-0132</td> <td>gustavo0@adventure-works.com</td></tr> <tr><td>Ms. Catherine R.Abel</td> <td>747-555-0171</td><td>catherine0@adventure-works.com</td></tr> <tr><td>Ms. Kim Abercrombie</td> <td>334-555-0137</td><td>kim2@adventure-works.com</td></tr> <tr><td>Sr. Humberto Acevedo</td> <td>599-555-0127</td><td>humberto0@adventure-works.com</td></tr> <!-- and so on ..... --> </table> |
..and it should look a bit fancier…
Employee Name | Phone | |
---|---|---|
Mr. Gustavo Achong | 398-555-0132 | gustavo0@adventure-works.com |
Ms. Catherine R.Abel | 747-555-0171 | catherine0@adventure-works.com |
Ms. Kim Abercrombie | 334-555-0137 | kim2@adventure-works.com |
Sr. Humberto Acevedo | 599-555-0127 | humberto0@adventure-works.com |
Sra. Pilar Ackerman | 1 (11) 500 555-0132 | pilar1@adventure-works.com |
Ms. Frances B.Adams | 991-555-0183 | frances0@adventure-works.com |
Ms. Margaret J.Smith | 959-555-0151 | margaret0@adventure-works.com |
Ms. Carla J.Adams | 107-555-0138 | carla0@adventure-works.com |
Mr. Jay Adams | 158-555-0142 | jay1@adventure-works.com |
Mr. Ronald L.Adina | 453-555-0165 | ronald0@adventure-works.com |
Mr. Samuel N.Agcaoili | 554-555-0110 | samuel0@adventure-works.com |
Mr. James T.Aguilar | 1 (11) 500 555-0198 | james2@adventure-works.com |
Mr. Robert E.Ahlering | 678-555-0175 | robert1@adventure-works.com |
Mr. François Ferrier | 571-555-0128 | françois1@adventure-works.com |
Ms. Kim Akers | 440-555-0166 | kim3@adventure-works.com |
Ms. Lili J.Alameda | 1 (11) 500 555-0150 | lili0@adventure-works.com |
Ms. Amy E.Alberts | 727-555-0115 | amy1@adventure-works.com |
Ms. Anna A.Albright | 197-555-0143 | anna0@adventure-works.com |
Mr. Milton J.Albury | 492-555-0189 | milton0@adventure-works.com |
I’ve done all this with just the nth-child CSS Selector. However, there are others that can be used to select particular elements of ‘naked’ structures. The Rosetta Stone and Cribsheet gives a much fuller list of selectors, pseudo-classes and pseudo-elements, particularly of attribute addressing (which doesn’t help us as our tables haven’t got attributes). Before you use them, check on the Quirks Mode site for compatibility with current browsers, and check on The W3.ORG site for the details, and plenty of examples of their use
Selector | Function | Example | What Example does |
---|---|---|---|
* selector | Selects all elements | * {font: 9pt Arial, Helvetica, sans-serif;} | Makes all elements have the specified font |
> selector | Selects direct children of an element | div.listing > p{ margin: 0; font: 11pt “Courier New”,Courier,monospace; } |
Assign style to all paragraphs directly within div.listing |
+ selector | Selects the following sibling of an element | h2 + p {margin-bottom: 10pt} | make first paragraph after n H2 heading with a bottom margin of 10pt |
[attr] selector | Selects an element with a certain attribute or attribute value | p[align=right] {float: right;border: 1px solid silver; text-align: left; padding: 15px; width:300px; } |
float any text with the attribute ‘align=”right”‘ to the right in a box 300px wide |
:before and :after | Insert content before or after an element | p.quote:before { content: open-quote; }
p.quote:after { content: close-quote; } |
put a quote before and after the start of any paragraph whose class is ‘quote’\ |
:first-child and :last-child | Select first and last children of an element | table td:first-child{ font-variant: small-caps; } table td:last-child{ font-variant: small-caps; } |
make the first and last column of the table render in small-caps. |
:first-line and :first-letter | : Select the first line or the first letter of an element | p.start:first-letter { line-height: 100%; float: left; font-size: 280%; } p.start:first-line { font-variant: small-caps; } |
makes a large first letter, floated to the left, and puts the fist line in small-caps |
~ selector | Selects the general next sibling(s) of an element | h3 ~ p {margin-left:40em} |
give every paragraph following an H3 a margin of 40em |
:first-of-type | The first sibling element of its type | td:first-of-type { font-weight: bold; } | makes the first column in a table bold |
:last-child | the last sibling | td:last-child { font-variant: small-caps; } | makes the last column text smallcaps |
:last-of-type | The last sibling element of its type | td:last-of-type { font-variant: small-caps; } |
makes the last column text smallcaps |
:only-of-type | The only child of its type | td:only-of-type {font-weight: normal; font-variant: normal;} | make the font normal if there is only one column |
:contains(‘text’) | now withdrawn for some reason! | ||
:empty | Empty elements (without content) | td:empty { background: silver; } | give a cell a silver background if it contains no text |
:nth-child(an +b)) |
Selects elements according to a formula specifying that the element has an +b-1 siblings before it in the document tree |
tr:nth-child(odd) {background-color:gray;} p:nth-child(4n+1) { color: navy; } p:nth-child(4n+2) { color: green; } p:nth-child(4n+3) { color: maroon; } p:nth-child(4n+4) { color: purple; } |
make the odd rows of a table have a gray background the next four examples alternate between four colours |
:nth-last-child(an +b)) |
Selects elements according to a formula specifying that the element has an +b-1 siblings after it in the document tree |
tr:nth-last-child(-n+2) {background-color:gray;} |
The last two rows in the table should have a gray background |
:nth-of-type (an +b)) |
Selects elements according to a formula specifying that the element has an +b-1 siblings before it with the same name in the document tree |
img:nth-of-type(2n+1) { float: right; } img:nth-of-type(2n) { float: left; } |
float alternating images in the same document level, left and right |
:nth-last-of-type(an +b)) |
Selects elements according to a formula specifying that the element has an +b-1 siblings after it with the same name in the document tree
|
tr:nth-of-type(n+2):nth-last-of-type(n+2) {background-color:silver;} |
make the odd rows of a table(Make the first and last row have a background of silver |
Let’s take a different example. Here is a PowerShell script. It is getting information about the SQL Server-related services that are running on a list of servers that I’ve supplied just to check that they are running OK. Whatever. The point is that I’ve once more got stuck with a table result that looks pretty difficult to format nicely.
1 2 3 4 5 6 |
# get the SQL Server service details from the list of servers and format them into an HTML table Get-WmiObject -ComputerName 'PhilFtest.factorFactory.com','ltPhilF' ` -property '__Server,Caption, Description, Name, Status, Started, StartMode' ` -class Win32_Service ` -filter "(NOT Name Like 'MSSQLServerADHelper%') AND (Name Like 'MSSQL%' OR Name Like 'SQLServer%')" ` | ConvertTo-HTML -Property '__Server','Caption', 'Description', 'Name','Status', 'Started', 'StartMode' -fragment |
You’ll get an output somewhat like this (I’ve kept it short for demo purposes)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<table> <colgroup> <col/> <col/> <col/> <col/> <col/> <col/> <col/> </colgroup> <tr><th>__Server</th><th>Caption</th><th>Description</th> <th>Name</th><th>Status</th><th>Started</th><th>StartMode</th></tr> <tr><td>PHILFTEST</td><td>MSSQL$SQL2000</td><td></td><td>MSSQL$SQL2000</td> <td>OK</td><td>True</td><td>Auto</td></tr> <tr><td>PHILFTEST</td><td>SQL Server (SQL2005)</td><td>Provides storage, processing and controlled access of data and rapid transaction processing.</td><td>MSSQL$SQL2005</td><td>OK</td><td>True</td ><td>Auto</td></tr> <tr><td>PHILFTEST</td><td>SQL Server (SQL2008)</td><td>Provides storage, processing and controlled access of data, and rapid transaction processing.</td><td>MSSQL$SQL2008</td><td>OK</td><td>True</t d><td>Auto</td></tr> <tr><td>LTPHILF</td><td>SQL Full-text Filter Daemon Launcher (MSSQLSERVER)</td><td>Service to launch full-text filter daemon process which will perform document filtering and word breaking for SQL Server full-text search. Disabling this service will make full-text search features of SQL Server unavailable.</td><td>MSSQLFDLauncher</td><td>OK</td><td>False</td><td>Manual</td></tr> <tr><td>LTPHILF</td><td>SQL Server (MSSQLSERVER)</td><td>Provides storage, processing and controlled access of data, and rapid transaction processing.</td><td>MSSQLSERVER</td><td>OK</td><td>False</ td><td>Manual</td></tr> <tr><td>LTPHILF</td><td>SQL Server Agent (MSSQLSERVER)</td><td>Executes jobs, monitors SQL Server, fires alerts, and allows automation of some administrative tasks.</td><td>SQLSERVERAGENT</td><td>O K</td><td>False</td><td>Manual</td></tr> </table> |
…but we can create something rather easier on the eye by defining some styles.
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 |
/* do the basic style for the entire table */ table { border-collapse: collapse; border: 2px solid #853a07; color: #452812; font: 10pt "Times New Roman", Times, serif; } /* give some sensible defaults just in case it is an old browser */ table td { border: 1px solid #c24704; vertical-align: top; background: #fdf5f2; } table th { border: 1px solid #fef7ef; padding: 8pt 2pt 5pt 2pt; color: white; font-weight: normal; vertical-align: top; background: #562507; } /* Now emphasise the first column right border */ table td:first-of-type { font-weight: bold; font-variant: small-caps; border-right: 2px solid #c24704; } /* and do a warp and weft effect */ table tr:nth-child(even) td:nth-child(odd){ background: #ffedd9; } table tr:nth-child(even) td:nth-child(even){ background: #fcf5ef; } table tr:nth-child(odd) td:nth-child(odd){ background: #ffe0bd; } table tr:nth-child(odd) td:nth-child(even){ background: #f9e4d4; } table th:nth-child(even){ background: #703009; } |
that will give this, once the style has been applied to the table
__Server | Caption | Description | Name | Status | Started | StartMode |
---|---|---|---|---|---|---|
PHILFTEST | MSSQL$SQL2000 | MSSQL$SQL2000 | OK | True | Auto | |
PHILFTEST | SQL Server (SQL2005) | Provides storage, processing and controlled access of data and rapid transaction processing. | MSSQL$SQL2005 | OK | True | Auto |
PHILFTEST | SQL Server (SQL2008) | Provides storage, processing and controlled access of data, and rapid transaction processing. | MSSQL$SQL2008 | OK | True | Auto |
LTPHILF | SQL Full-text Filter Daemon Launcher (MSSQLSERVER) | Service to launch full-text filter daemon process which will perform document filtering and word breaking for SQL Server full-text search. Disabling this service will make full-text search features of SQL Server unavailable. | MSSQLFDLauncher | OK | False | Manual |
LTPHILF | SQL Server (MSSQLSERVER) | Provides storage, processing and controlled access of data, and rapid transaction processing. | MSSQLSERVER | OK | False | Manual |
LTPHILF | SQL Server Agent (MSSQLSERVER) | Executes jobs, monitors SQL Server, fires alerts, and allows automation of some administrative tasks. | SQLSERVERAGENT | O K | False | Manual |
Putting it all together
Curiously, after years of happily using Internet Explorer as a simple way of displaying data as part of a scripting process, I initially fell foul of using Internet Explorer 9 to display HTML5 and CSS 2/CSS3. Here is the script that I currently use to automatically display this, and any other grid of data in PowerShell. The principle is the same for every other scripting language.
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 55 56 |
#firstly, let's get the in-line stylesheet in place, and the rest of the header for the HTML Document. # as we want to render just the table we take out margins. (body {margin: 0 0 0 0;}) $head= @' <title>SQL Server Services</title> <style type="text/css"> /* do the basic style for the entire table */ body {margin: 0 0 0 0;} table { border-collapse: collapse; border: 2px solid #853a07 ; font-size: 10pt ; color: #452812; font-family: "Times New Roman", Times, serif; } /* give some sensible defaults just in case it is an old browser */ table td {border: 1px solid #c24704; vertical-align: top; background-color: #fdf5f2;} table th {border: 1px solid #fef7ef; padding: 8pt 2pt 5pt 2pt; color:white; font-weight: normal; vertical-align: top; background-color: #562507;} table td:first-of-type { font-weight:bold; font-variant: small-caps; border-right: 2px solid #c24704;} table tr:nth-child(even) td:nth-child(odd){ background-color: #ffedd9; } table tr:nth-child(even) td:nth-child(even){ background-color: #fcf5ef; } table tr:nth-child(odd) td:nth-child(odd){ background-color: #ffe0bd; } table tr:nth-child(odd) td:nth-child(even){ background-color: #f9e4d4; } table th:nth-child(even){ background-color: #703009; } </style> </head> '@ # get the SQL Server service details from the list of servers and format them into an HTML document $content=Get-WmiObject -ComputerName 'PhilFactor.com','ltPhilF' ` -property '__Server,Caption, Description, Name, Status, Started, StartMode' ` -class Win32_Service ` -filter "(NOT Name Like 'MSSQLServerADHelper%') AND (Name Like 'MSSQL%' OR Name Like 'SQLServer%')" ` | ConvertTo-HTML -Property '__Server','Caption', 'Description', 'Name','Status', 'Started', 'StartMode' ` -head $head $exploder = new-object -comobject "InternetExplorer.Application" $exploder.visible = $true $exploder.width=640 # width of the table. $exploder.menubar = $false # kick out the menu bar $exploder.toolbar = $false # and the toolbar $exploder.statusbar = $false # who wants a status bar $exploder.resizable = $true # but you may want this $exploder.AddressBar=$false # $exploder.navigate2('about:offlineinformation') # because it forces the creation of a document which defaults to # IE9 standards mode (about:blank is in 'compatibility mode' by default!) While ($exploder.Busy) {} #wait for the docuent to load $exploder.document.IHTMLDocument2_write($content) #and put in our content instead |
If you can’t see CSS2-3/HTML5 views of a page.
If you can’t see all those CSS2-3/HTML5 effects in a page in IE8 or IE9, it probably means you’re in ‘compatibility mode’. If your browser window has a thin gray border, shucks, you’re in Compatibility mode.
The developers of Internet Explorer were faced with a change of direction towards HTML/CSS standards-compliance after years of attempting an ultimately futile policy of creating their own de-facto browser standard. In order to create a browser that could still render websites that were written to the Internet Explorer standard, with IE8 and IE9, they have had to introduce a notion of ‘compatibility mode’ which is extraordinarily elaborate and even involves the maintenance of a register of the ‘compatibility’ of various websites. The intricacies of this mechanism would wear out the patience of even the keenest reader so I’ll say little more other than to say that one can force the browser to stop fleeing standards-mode like a frightened rabbit by …
- selecting, from the menu, TOOLS -> Compatibility View Settings, and unchecking ‘display intranet sites in compatibility view‘ (and, if necessary, ‘display all websites in compatibility view’ ).
- From the menu, Tools -> Internet Options. Click on the advanced tab, Look for the browsing section, and in there find ‘automatically recover from page layout errors with Compatibility view‘. Unclick it.
- If all else fails… Hit F12 in order to get into the ‘developer pane’. Here, you will see the last two menu item ‘Browser Mode and ‘Document Mode’. Make sure that these are set to IE9.
I know of no way in scripting to force IE9 to read a dynamic page in standards mode, other than by using the HTML5 doctype (<!DOCTYPE HTML>) in the page you write to the instance. In the script above, I’ve used guile rather than science.
Conclusions
The use of the CSS structural pseudo-class and pseudo-elements hold out the hope that we cen do what we want with the rendering of HTML structures without having to add a lot of classes to the elements, but instead just doing it by specifying their position in the DOM. I have to admit that I miss the suddenly-withdrawn :contains pseudo class which for the first time allows us to specify an element by its content. We hope that wider counsels prevail. For those of us who are having to render such XHTML fragments as tables and lists, generated from PowerShell and SQL, these extensions to CSS are a real boon.
Load comments