Tame alternate stylesheets

n: smd_style | v: 0.30 | f: Development / Textpattern plugins

Documentation for the Textpattern plugin smd_style by Stef Dawson follows this short message from our sponsor ;-)

Plugin list buttonPlugin download buttonCompressed plugin download button

smd_style

Manage alternate stylesheets and (optionally) switch between them via Javascript. Alternatively, switch between CSS class names on arbitrary objects to effect style changes.

Features:

  • Provide a list of style sheet names derived from:
    • a given list
    • any article field
    • a <txp:variable />
    • a URL variable
    • some combination of the above
  • Use either the built-in stylesheet switching ability of modern browsers or the plugin’s javascript switchers
  • Current style stored in a cookie: can be instructed to load that style first on subsequent pages
  • Compatible with Txp stylesheets, ‘real’ stylesheets and the rvm_css plugin
  • Optionally build your own stylesheet syntax via a form/container
  • Put Txp tags inside your stylesheets if you wish (requires Txp-style sheet processing, i.e real_sheets must be off)

Author

Stef Dawson

Installation / Uninstallation

Download the plugin from either textpattern.org, or the software page, paste the code into the Txp Admin -> Plugins pane, install and enable the plugin. Visit the forum thread for more info and to report the success (or otherwise) of this plugin.

To uninstall, simply delete from the Admin -> Plugins page.

Usage

There are two tags available: one for use in the <head> of your page to set up the alternate styles, and the other adds a javascript stylesheet switcher to the page, wherever you want it to appear.

smd_style

Somewhere in your HTML <head> tag, add a call to smd_style to include a list of alternate stylesheets. Use the following attributes to customise the output:

Attributes

  • sheets : a comma-separated list of stylesheets to include. These are their Txp names as given on the Presentation->Styles tab. See sheet names for more
  • form : the Txp form in which to build your own <link> tags. See replacement tags. If not specified (and no container is used) a standard HTML link will be output
  • use_default : the list of stylesheets given in the sheets parameter does not usually contain the ‘default’ stylesheet associated with the section. This allows you to always include a ‘global’ sheet with core rules in it and have the alternates override/fill in the gaps. If, however, you wish smd_style to output the default stylesheet so you don’t have to do it elsewhere, specify use_default="1"
  • promote_recent : Determine what to do when the page loads and the visitor has already switched to an alternate stylesheet. This can be useful to try and help prevent ‘flashes of unstyled content’ in good browsers. Can take one of three values:
    • 0 : do nothing, i.e. sheets are always loaded in the given order
    • 1 (the default) : any style sheet that has been chosen by the visitor will be loaded as soon as possible after any core sheets (i.e. sheets that you want to be non-alternate). Thus if you have 2 ‘core’ sheets and the visitor chooses the alternate sheet that used to be #4 in the list, it will now be loaded 3rd
    • 2 : any style sheet that has been chosen by the visitor will be loaded first, i.e. the chosen sheet will always be promoted to the #1 slot
  • skip : in the list of sheets, skip this number of sheets before starting to label them as ‘alternate’. The special default setting of auto behaves thus:
    • if use_default="1", the first sheet in the list will be skipped (i.e. will always be a true stylesheet). Equivalent to skip="1"
    • if use_default="0" the first sheet in the list will be an alternate stylesheet (i.e. equivalent to skip="0")
  • skip_titles : in the list of sheets, skip this number of sheets before starting to add title attributes. The special default setting of auto works the same as for the skip attribute. Note that some combinations of skip/skip_title are not permitted by the HTML specification so are disallowed. See skipping for more details
  • show_empty : by default, if any field (e.g. a custom field) has no value assigned to it and you try to read a sheet name, the field will be ignored. Setting this to 1 will include a stylesheet with the name of the field itself. See Example 5 for a practical application of this
  • real_sheets : normally, standard css.php?n=sheet_name references are output. If you prefer ‘real’ stylesheet filename references, set this attribute to 1. Note you will have to ensure that the stylesheet files really exist on your server in the given directory (see sheets_dir)
  • sheets_dir : location (relative to the root of your textpattern installation) where your stylesheets are to be found if using real_sheets="1". If you have the rvm_css plugin installed it will default to the directory specified in Admin -> Preferences -> Advanced -> Style directory
  • parse_tags : if you wish to be able to put Txp tags inside your style sheets and have them parsed, set this attribute to 1. Note that this only works if real_styles="0" because there’s no way for Textpattern to intercept a direct call to a stylesheet. Using this feature also incurs a (small) time penalty as each sheet is parsed
  • delim : the delimiter to use between sheets. Default is the comma (,)
  • paramdelim : the delimiter to use between additional sheet information. Default is the colon (:)

Sheet names

When using the sheets attribute, if you precede any sheet name with a ? the Txp environment will be searched for a matching location. For example, sheets="core, ?custom3" would load core.css and then look in the custom3 field for a further list of stylesheets to load. The order in which the locations are searched is: 1) Article fields; 2) <txp:variable />; 3) The URL. If a ? value is supplied and a suitable location cannot be found, the value you gave will be used verbatim.

You may also specify up to two additional pieces of information in each sheet definition: 1) the sheet’s “pretty name” that people will see, and 2) its media type (usually screen). To do this, separate the values with colons (unless changed via the paramdelim attribute) like this:

<txp:smd_style use_default="1"
     sheets="core, cats:Cats of the world, printer:Print layout:print" />

That would output something similar to the following:

<link rel="stylesheet" type="text/css" media="screen"
     title="default" href="http://site.com/textpattern/css.php?n=default" />
<link rel="alternate stylesheet" type="text/css" media="screen"
     title="core" href="http://site.com/textpattern/css.php?n=core" />
<link rel="alternate stylesheet" type="text/css" media="screen"
     title="Cats of the world" href="http://site.com/textpattern/css.php?n=cats" />
<link rel="alternate stylesheet" type="text/css" media="print"
     title="Print layout" href="http://site.com/textpattern/css.php?n=printer" />

Replacement tags

If you don’t like the standard output or wish to fashion your own <link> tags then you can specify container content or a form with which to process the results. There are replacement variables you can employ within your layout to insert the relevant content for each stylesheet:

  • {smd_style_name} : the stylesheet name as given in the Txp Styles tab
  • {smd_style_url} : the complete URL (Txp-style or ‘real’ URL depending on the real_sheets attribute)
  • {smd_style_media} : the media type of stylesheet (e.g. screen, print…)
  • {smd_style_rel} : the stylesheet relationship (either ‘stylesheet’ or ‘alternate stylesheet’)
  • {smd_style_title} : the human-friendly title you have assigned to the stylesheet. If not specified, it defaults to the stylesheet name
  • {smd_style_counter} : the current stylesheet number being processed
  • {smd_style_total} : the total number of stylesheets in the list

Skipping

The HTML specification allows for three types of stylesheet:

  • Persistent : ones that are forced on the user unless they choose “No Styles” from their browser menu
  • Preferred : ones that are always loaded but can be swapped out for others
  • Alternate : ones that are not loaded by default but can be switched in and out freely by the user

With a combination of use_default, skip and skip_title you can offer any of these types. Some combinations of skip and skip_title are not permitted because they would render illegal HTML (e.g. the case when you have an alternate stylesheet without a title).

To try and make it a little easier to visualise, here is a table that shows which of the stylesheet flavours are served with varying values of these attributes. Assume you have two stylesheets labelled A and B. Sheet A will be set to:

  skip=0 skip=1
skip_title=0 Alternate Preferred
skip_title=1 (disallowed) Alternate Persistent

The same logic applies if using the auto parameter and/or the use_default attribute. The first sheet in the list will become one of those flavours of Stylesheet where the values of skip and skip_title intersect. If using higher values, the first few sheets become those particular flavours.

smd_styleswitch

Requires jQuery to be loaded on your page, before the call to smd_styleswitch. Tested with v1.7.

On its own the smd_style tag gives the visitor the ability to switch styles via the ‘View alternate stylesheet’ facility of modern browsers. If you wish to offer them a way of instantaneously switching (and remembering) the chosen theme from within your content, use smd_styleswitch somewhere in the flow of your page to insert a stylesheet switcher.

Alternatively, you may decide not to bother with traditional stylesheet switching at all and may prefer to simply switch class names on certain elements of the page to effect changes.

It can be customised as follows:

Mode 1: Stylesheet switcher

Attributes

  • wraptag : standard HTML tag to wrap the entire switcher with. Default is ul. If you do not specify this attribute you must create your own container somewhere else in the page and tell the plugin its ID or class so it can find it
  • html_id : HTML ID to apply to the wraptag. Default is unset. If you have not specified a wraptag, you may give the ID of your own container so the switcher can be inserted
  • class : CSS class to apply to the wraptag. Default is smd_switcher. If you have not specified a wraptag, you may give the class name of your own container so the switcher can be inserted
  • break : Default is li. Can be either:
    • a standard HTML tag to wrap each stylesheet name and link with.
    • text to place between each element, such as break="--" (see break_is_tag)
  • break_is_tag : if the break attribute is a tag, this should be set to 1 (which it is by default). If you wish to use something else between each stylesheet name (e.g. break=" | ") set this to 0
  • linkclass : CSS class to apply to each stylesheet link that is inserted. Default is smd_styleswitch
  • linkloc : the HTML attribute to compare for a match. When you click a link, in order to determine which one has been clicked, some unique information must be present in each link. You might choose the anchor’s ‘name’ attribute (in which case, use linkloc="name") or perhaps the ‘rel’. Default: rel (in this mode) or name in class switching mode
  • activeclass : CSS class to indicate the currently selected stylesheet link. Default is smd_currstyle
  • alt_only : if set to 1 will only offer stylesheets with a rel that includes ‘alternate’ in the list. The default behaviour (0) is to list all stylesheets on the page that contain a ‘title’ attribute
  • sort : set to 1 (the default) to sort the stylesheets in alphabetical order. If set to 0, the most recently used stylesheet will be shown at the head of the list
  • case_sensitive : whether the sort is case sensitive (1) or not (0). Default is 0
  • expiry : number of days after which the smd_style cookie that holds the current style information remains valid. Default is 30. Set to 0 to disable cookie storing
  • form : if you prefer to make your own links instead of using the default anchor tags, either use the tag as a container or specify a Txp form with which to process each link. Default: unset

The stylesheet switcher that is inserted at the given location consists of an anchor tag with a link to the new stylesheet. The text within the anchor is the stylesheet title (if supplied) or its name if not. Note that the switcher will pick up all stylesheets that have a title attribute so if any of your other stylesheets not controlled by the plugin have titles, they will be included as well. This could be considered a feature(!)

If you are a neatness buff and prefer that all javascript goes in the <head>, using wraptag="" will allow you to insert the <txp:smd_styleswitch /> tag in the head of your document while locating your switching container elsewhere. Just tell the plugin either the html_id or class of your switch container and the switcher will be inserted there.

Mode 2: Class switcher

The other way to use the tag is as a class changer. You may target any selector on the page and offer a list of class names that will be applied to that element when the relevant switch link is chosen.

Attributes

Takes the same attributes as above, with the exception that alt_only is ignored. In addition:

  • byclass : this puts the plugin into “class switching mode”. It works like the sheets attribute of the smd_style tag. Specify a list of class names that users may choose between. If you follow the class name with a colon (:) you may specify a “friendly” name to show to people instead of the class name itself. The special class name default removes all classes that have been applied to the selector. See example 7.
  • destination : the DOM element ID you wish to add classes to, e.g. destination="#my_div" or destination=".my_class". Default: body
  • clean : if this is set to 1, anything inside your designated wraptag container will be removed before adding the switcher content. Default: 1
  • delim : the delimiter to use between items in the byclass attribute. Default: comma
  • paramdelim : the delimiter to use between class and name in the byclass attribute. Default: colon

If you put more than one smd_styleswitch tag in this mode, each will function independently so you can add class combos to the same element (see example 8). Very useful for specifying two different lists of classes: one for “screen” styles and one for “print styles” that operate independently.

But beware the following caveats when putting multiple smd_styleswitch tags on a single page. For hassle-free operation, you should:

  • attach each tag to a unique element. In other words, change the class or html_id to something different in each tag
  • ensure that linkclass is also unique for each tag, otherwise you may get odd results when choosing a style
  • be aware that using the same classname twice (e.g. default in two tags) will not switch properly if you are using a linkloc="id" because the plugin will try and assign the same name to two different IDs, which is illegal DOM markup

Gotchas

  • Make sure jQuery can be found by the page, or smd_styleswitch will do nothing
  • If using certain wraptag elements (e.g. the default “ul”), the HTML validator may complain that the element is “unfinished” because it does not render the javascript which inserts the remaining ‘li’ tags. If this bothers you, set wraptag="" and specify your own container, filling it with an empty child tag (e.g. <li>&nbsp;</li>). The plugin will empty the contents of the container before adding the links as long as the clean="1" is used in the tag (which it does by default)
  • You must use one (or both) of either class or html_id attributes, otherwise the plugin will not be able to attach itself. the html_id is used in preference to class if both are used

Examples

Example 1: alternate stylesheets

<txp:smd_style
     sheets="yellow, green, brown, blue, pink, black" />
<txp:smd_styleswitch wraptag="" />

Somewhere further down the page you would have to add:

<ul class="smd_switcher"></ul>

That will add six alternate stylesheets to the page and add a switcher wherever your <ul> appears on the page. A few modifications are possible:

  • with rvm_css installed, adding real_sheets="1" to the smd_style tag would change the URLs to ‘real’ CSS file paths
  • adding use_default="1" will add the current section’s stylesheet to the list as well. If it happens to be the same as one of the ones in the list already, it will only be used once
  • adding skip="2" will cause the ‘yellow’ and ‘green’ sheets to be static (non-alternate)
  • using promote_recent will change the load order as follows:
    • 0 : load order will always be yellow, green, brown, blue, pink, black
    • 1 : promote the chosen sheet up the list. With use_default="1" skip="2", if the visitor chose ‘pink’ as their theme and refreshed the page, the load order would be: yellow, pink, green, brown, blue, black (yellow and pink would be listed as “static” stylesheets)
    • 2 : force whichever sheet was most recently chosen to be the first loaded. Again, if the visitor chose ‘pink’ as their preferred sheet and refreshed the page, the load order would be pink, yellow, green, brown, blue, black (pink and yellow would be static sheets)

Example 2: using txp_variable

To set up the names of the stylesheets you want to include in a <txp:variable /> try this:

<txp:variable name="alt_styles"
     value="girls:Girly theme, boys:Lads only" />
<txp:smd_style sheets="?alt_styles" />

Example 3: persistent sheets

Grab the default sheet, the “fixed” sheet (and make them both ‘static’) then look in the custom field labelled alt_styles and the URL variable mytheme for more sheet definitions.

<txp:smd_style sheets="fixed, ?alt_styles, ?mytheme"
     use_default="1" skip="2" />

Note that no distinction is made where to find the variables, they are just checked in order and the first one it finds that matches (if at all) will be used. Thus if you had a custom field labelled alt_styles and someone put site.com/my_page?alt_styles=two,three,four on the URL, you would still have them read from the custom field because it is ‘higher’ in the hierarchy. One caveat is that if the custom field is empty, the next place in the list is checked until all locations are exhausted, though you can ignore empty fields with show_empty="0".

Example 4: custom link layout

For a non-HTML DTD you may need to drop the trailing / on the <link> elements, so you could do this:

<txp:smd_style sheets="blue, red, green">
 <link rel="{smd_style_rel}" type="text/css"
     media="{smd_style_media}" href="{smd_style_url}"
     title="{smd_style_title}">
</txp:smd_style>

Example 5: show_empty

The show_empty attribute seems fairly pointless on the surface, but imagine this scenario: you set up a custom field called default_style. When authoring articles, people can insert the name of a stylesheet in there to style the page with. But if they leave it blank you could ensure the page renders with at least some default content by creating a stylesheet called “default_style” (i.e. the same name as the custom field) and using a tag like this:

<txp:smd_style sheets="?default_style"
     show_empty="1" />

If the custom field is empty, the plugin converts the request to <txp:smd_style sheets="default_style" />.

Example 6: tag parsing

<txp:smd_style sheets="first, second, third"
     parse_tags="1" />

If one of your stylesheets contained the following:

.filler {
   background:url(<txp:site_url />images/bg.jpg);
   color:red;
}

The Txp tag would be replaced with the contents before being served. This does have a performance penalty as the stylesheet is fetched and parsed, but can be very useful. You may use <txp:php></txp:php> tags in the stylesheet as long as the admin preference to allow page level PHP is enabled. Many thanks to akokskis for some of the code from ako_cssParse that enables this feature.

Example 7: class switching

<txp:smd_styleswitch
     byclass="default:Normal, mini:Small,
       maxi:Large" class="switcher" />

That tag will show a three-way class switcher containing three anchors as an unordered list. When you click:

  • Small the mini class is added to the <body> tag
  • Large the maxi class replaces the mini class
  • Normal either mini or maxi are removed (whichever was in force at the time)

Thus with appropriate rules in a stylesheet you can switch the font size of the document on the fly. For example:

body {
  font-size:1em;
}
body.mini {
  font-size:.8em;
}
body.maxi {
  font-size:1.3em;
}

If you loaded your stylesheet with media="print" then the styles would only be applied to the printed version of the page.

Example 8: multiple class switchers

Let’s add a second smd_styleswitch tag to the page, in addition to the one in example 7.

<txp:smd_styleswitch
     byclass="default:Black on white, yob:Yellow on blue,
       gob:Green on black" linkclass="smd_colours" />

Notice first that this one takes linkclass. This is so that the two switchers are uniquely addressable — the first used the default linkclass of smd_styleswitch. Without this, the two switchers will clash and things won’t work properly.

The second switcher will be rendered and the two work independently; visitors can choose one style from each switcher. So they might click:

  • Large and Yellow on blue : both maxi and yob classes would be added to the <body> tag
  • Large and Green on black : maxi and gob classes would apply to the body
  • Normal and Green on black : just the gob style is applied
  • Normal and Black on white : both styles are removed. Note that any other styles you may have manually applied are left untouched

Again, with suitable classes you can allow people to combine the look of a page to their tastes. By default, their preferences are saved in the smd_style cookie so each page will have the same style applied.

Changelog

  • 12 Oct 2008 | 0.10 | Initial release
  • 18 Oct 2008 | 0.20 | Added parse_tags (thanks akokskis / johnstephens) ; added skip_title ; fixed invalid link tags ; real_sheets now work without a form/container
  • 17 Nov 2011 | 0.30 | smd_styleswitch can now switch classes too ; added html_id and form attributes ; cookie can be disabled ; jQuery 1.3 fix

Source code

If you’d rather dive in and out of functions, you’ll need to step into the view source page.

Legacy software

If, for some inexplicable reason, you need a legacy version of a plugin, it can probably be found on the plugin archive page.

Experimental software

If you’re feeling brave, or fancy skateboarding into volcanos, you can test out some of my beta code. It can be found on the plugin beta page.