Question for the
CSS gurus:
I have several plugins that generate formatted output; for example, the action tracker produces tables of actions that are formatted by code. I can't use the standard TWiki tables to do this, for a number of technical reasons; also, I need the tables to really stand out, so I normally equip them with a garish orange header bar.
I would like to be able to use
CSS so that admins and skin authors can configure the looks when they change the other TWiki style sheets. But how can I do this? How do I add a new set of styles? I really want it so any number of plugins can add any number of additional stylesheets to the style cataract.

When a plugin is installed it usually just inserts files, and doesn't edit existing files, though that
is an option if there is no other way.
--
CrawfordCurrie - 17 Apr 2005
give then classes instead of including a style. As an example, the "garish orange header bar", give it a class. ie: th class= "somename". This would then have to be put into stylesheet though...maybe add a class already in the stylesheet, such as use the webbgcolor?
--
BruceRProchnau - 17 Apr 2005
Or add a custom color into stylesheet for accent colors
--
BruceRProchnau - 17 Apr 2005
I would use a plugin namespace as classname. Class names should start with lowercase, or TWiki will make a wikiword link from it (and yes, underscores in css class names are disfavored by
W3C). You can also use multiple classnames separated by spaces.
For instance
<table class="actionTracker"><tr><td>bla</td></tr></table>
and in your stylesheet:
table.actionTracker {background-color:orange;}
table.actionTracker td {padding:2px; background-color:yellow;}
--
ArthurClemens - 17 Apr 2005
Arthur and Bruce, I think it what is clear to us is that we would create classes instead of hardwired style information. What is not clear is how we define the styles in those classes from our plugins. See, a plugin should install by only adding files (when unzipping). It should not have things that need to be added to the existing style sheet. So the real question (I think) is... how do we get the above
table.actionTracker style info into somewhere where it is being read as a style sheet.
--
ThomasWeigert - 17 Apr 2005
use an existing class already in stylesheet
--
BruceRProchnau - 17 Apr 2005
the stylesheet can be linked anywhere in the html page. More so, if two "conflicting" styles are included in the same page, the last one wins.
So, perhaps the "best" way is that each plugin distribute it's own stylesheet (with the proper namepace in the classes) in the pub dir, and use the preRenderingHandler to insert them in the "proper" place OR include the style each time a tag is rendered by the plugin.
--
RafaelAlvarez - 17 Apr 2005
When you link css in the topic text the page will no longer validate, though it will work in all modern browsers.
--
ArthurClemens - 17 Apr 2005
We could come up with a flexible, though bit complex approach: similar to pattern skin that has a topic in TWiki web with attachment css files, we could use a PluginCss topic that has a
pluginstyles.css file. That file imports css styles for each plugin.
Diagram of this:
The same thing could be done with
UsingTopicToDefineCSS when that works.
--
ArthurClemens - 17 Apr 2005
Is this going to take a big performance hit? What about just embedding styles inline in the
HTML? As in
<STYLE TYPE="text/css" MEDIA=screen>
<!--
BODY { background: url(foo.gif) red; color: black }
P EM { background: yellow; color: black }
.note { margin-left: 5em; margin-right: 5em }
-->
</STYLE>
I guess this can only go into the HEAD? Which brings up the issue again of giving plugins an easy way to add something to the HEAD.
--
ThomasWeigert - 17 Apr 2005
When using the attachments approach the css gets cached. When you put it in the topic text you will load it each topic visit.
If you can write into the head <style> block that would be the easiest: for each plugin a line
@import url("ThePluginStyle.css");
--
ArthurClemens - 17 Apr 2005
Arthur and Crawford, can you please, in light of the discussion above, comment on what I have been doing recently in my plugins. I have been putting
<div> blocks around generated text that I wanted to be customizable and put into the plugin precedence variables
CSS that would be inserted into the
<div> e.g.,
style="border: thin red dotted" or
class="twikiTopicAction" or similar.
This way the user has control over the style or class applied to some element.
--
ThomasWeigert - 17 Apr 2005
Thomas, what you are doing is fine AFAIK but my goal is to use
CSS classes, so skin authors etc can override my defaults. That means adding a stylesheet attachment to the load chain, as I understand it. Embedding styles in the
HTML is not a credible solution IMHO. The
preRenderingHandler is called from all sorts of odd contexts, and the plugin author is left with a horrible job to sort through when it should, and when it should not, add the styles. I went through this when embedding the Javascript used by the action tracker, and it isn't easy to get right.
I like the sound of Arthurs proposal above, but I have a couple of concerns:
- If there is no plugin style defined, it has to fall back to
empty.css - yet again. Maybe browsers will be smart enough to cache this intelligently, maybe not
- I still have to edit a file in the release, to add my plugins style sheet to
pluginstyles.css.
Note that this problem - adding to the header - is the same problem as adding Javascript (or <META or any other tag) to the <head>, so I would really like a common solution.
I was going to propose a reasonably hairy handler for the plugins, but I realised that there is already a useful handler (in
DevelopBranch) viz the
postRenderingHandler. This can also be used to embed Javascript in the header. Unfortunately it requires a basic parse of the HEAD, but that isn't too much of a problem. For example, to add a stylesheet to the header you would do this:
sub postRenderingHandler {
my $added = 0;
return unless( $_[0] =~ /<head.*\/head>/i );
my $newStyle = CGI::style
( { type=>'text/css', media=>'all' },
'@import("%PUBURL%/%TWIKIWEB%/TartPlugin/Tart.css")' );
# expand %PUBURL% and %TWIKIWEB%
$newStyle = TWiki::Func::expandCommonVariables( $newStyle );
$_[0] =~ s!</head>!$newStyle</head>!;
}
Javascript can be added in exactly the same way.
--
CrawfordCurrie - 18 Apr 2005
Wouldn't that be necessary for all plugins?
postRenderingHandler would have to call
writeHeadStyle with
web and
topic and
filename as arguments.
--
ArthurClemens - 18 Apr 2005
Sorry, what is
writeHeadStyle ??
--
CrawfordCurrie - 18 Apr 2005
I tried to come up with a handler name to describe what you described above: writing the style in the html head block.
--
ArthurClemens - 18 Apr 2005
The tools at
CPAN for generating
HTML such as Lincon Stein's
CGI module have calls that let you build the header.
http://search.cpan.org/~lds/CGI.pm-3.07/CGI.pm#CREATING_A_STANDARD_HTTP_HEADER
:
I'm not clear if a plugin coul "load up" the header with style-sheet references that would be emitted later by the rendering engine.
How practical it would be and what the security issues invovled in "allowing" access to the header is another matter that needs to be considered.
--
AntonAylward - 18 Apr 2005
Anton, the difficulty is not how to write a header. The difficulty is how to collect up the information from all the plugins and other rendering code that should go into the HEAD section of the
HTML document (style sheet imports, java script).
--
ThomasWeigert - 19 Apr 2005
Wy would you need to collect information? Couldn't each plugin add a line (or two) to the head if needed?
--
ArthurClemens - 19 Apr 2005
Reset.
Anton, as Thomas says, this is about the HEAD block, not the HTTP header. The HTTP header is already cleanly handled in
modifyHeaderHandler.
Arthur, no, they couldn't. There are two reasons:
- Two plugins shouldn't add identical tags (e.g. JSCalendar @imports
) so they need to know what's there already
- There are circumstances where a plugin may need to modify a previously added tag e.g. to add a language parameter, or change an expiry time, or something like that.
I would much prefer to
parse the existing header and pass the parse tree to the plugins to let them deal with it rather than asking
them to do the parsing, but that's a fairly heaviweight solution that I just don't think is justified here. This is a rare enough requirement that the pattern I gave above is a good enough solution. I really don't think another handler is justified.
Further on this point, I'm thinking that the
JSCalendarContrib could publish a method that adds it to the header when called from another plugin's
postRenderingHandler, thus:
sub postRenderingHandler {
if( $calendarWasUsed ) {
TWiki::Contrib::JSCalendarContrib::addToHEAD( @_ );
}
}
(adding the JSCalendar's
@imports
should
not be done by default; the imports should
only be inserted when a calendar is actually used on a page, which is determined by plugins).
The nice thing about this approach is that it encapsulates the details of the tags in the contrib, so if they change when the Contrib is upgraded, the plugins are not impacted.
Other Javascripting contribs could do the same (e.g. the
JavaScriptSpreadSheetContrib that I've been thinking about for a while now)
--
CrawfordCurrie - 19 Apr 2005
Crawford, on the example above, wouldn't the addToHEAD need the code or whatever to add as argument, but the postRenderingHandler would get very different arguments? I guess I am wondering about the
@_ in your code above...
--
ThomasWeigert - 19 Apr 2005
I'm just passing the arguments array - the first entry of which is \$text - straight on to the addToHead function, so it can directly manipulate $_[0]
--
CrawfordCurrie - 19 Apr 2005