Skin Guidelines

Summary: A set of tips for skin design and packaging skins for distribution
Status: Stable
Version: 2007-07-21
Prerequisites: pmwiki 2
Maintainer: Hagan Fox

How A Skin Works

The main component of a skin is its template file. Pmwiki goes through (parses) the template and makes substitutions when it sees special layout markers (e.g. layout variables, also called skin directives).

The skin's template file is named either pub/skins/<skin-name>/<skin-name>.tmpl or pub/skins/<skin-name>/skin.tmpl where <skin-name> is all lower case with no spaces or hyphens. For example, a template file for "Foo Skin" would be pub/skins/foo/foo.tmpl. To have PmWiki use this skin, you'd add

## Use the Foo Skin.
$Skin = 'foo';

to your local/config.php local configuration file.

In the <skin-name>.tmpl file:

There are three required layout markers:

<!--HTMLHeader-->
Required in the <head> section of the template. PmWiki will insert tags (using $HTMLHeaderFmt) and CSS selectors (using $HTMLStylesFmt) in this location. Read more at LayoutAdvanced?.
<!--PageText-->
Required in the <body> section of the template. Pmwiki will place the page content in this location.
<!--HTMLFooter-->
Required in the <body> section of the template. Many recipes rely on this directive, and at some point the core may rely on it.

So a template file takes this basic form:

<html>
 <head>
  ...some header stuff...
  <!--HTMLHeader-->
  ...some more header stuff...
 </head>
 <body>
  ...some HTML...
  <!--PageText-->
  ...some more HTML....
  <!--HTMLFooter-->
 </body>
</html>

Of course the "header stuff" and "HTML" may include all kinds of things that may change dynamically in many ways.

Often a skin also has at least one CSS stylesheet file and a PHP file. The PHP file must be named either skin.php or <the skin's folder name>.php, (e.g. foo.php for a skin in the pub/skins/foo/ directory). A documentation text file should also be included in a skin's folder. Example files for a Foo Skin:

pub/skins/foo/
|-- README.txt       Documentation
|-- foo.tmpl         Template
|-- foo.css          CSS stylesheet
`-- foo.php          PHP script

Page Sections

Sections of output can be denoted using <!--Page...Fmt--> directives. The default sections can be suppressed by special page directives in wiki markup, where an author can use (:notitle:), (:noheader:), etc. to turn off components of the page.

When a section is "turned off", output is suppressed from the <!--Page...Fmt--> until the next directive, a closing <!--/Page...Fmt--> or the end of the template is reached, whichever comes first.

A page section can also be turned off in a recipe or local configuration script using markup like

SetTmplDisplay('PageXyzFmt', 0);

that will suppress the <!--PageXyzFmt--> section of the template.

Since PmWiki 2 beta 22, you can make your own Page...Fmt sections. The reason you might want to do this is that you can then use the SetTmplDisplay() function in a skin.php or config.php to show or hide these sections, or you can create a 5pmhlt%(:noxyz:) directive so it can be controlled with wiki markup.

Note that SetTmplDisplay() expects each <!--PageMySectionFmt--> to have a unique name. If you need to hide or show a bunch of small items scattered about the page, you are best off using CSS classes and the property display:none.

Skin directives that denote page sections

These are provided by default:

<!--PageHeaderFmt-->
This is a convenient section to place a logo, a link to the homepage, anything you wish to place in the head of the pages. This section may be suppressed with the (:noheader:) directive.
<!--PageTitleFmt-->
This section is the standard section for placing the title and group links. This section may be suppressed with the (:notitle:) directive.
<!--PageActionFmt-->
This section is used for wiki action links. It may be suppressed with the (:noaction:) directive.
<!--PageLeftFmt-->
This section is customarily used for placing a SideBar menu, in the form of

<!--wiki:$Group.SideBar $SiteGroup.SideBar--> . This section may be suppressed with the (:noleft:) directive.

<!--PageRightFmt-->
This section would be used for right-side content. This section may be suppressed with the (:noright:) directive.
<!--PageFooterFmt-->
This section may hold footer content, often links to page actions and a "last-modified" line. This section may be suppressed with the (:nofooter:) directive.

Special skin directives

PmWiki includes special markers for inserting content from a wiki page, markup in the template, a PHP function, or a file on disk.

<!--wiki:pagename[ pagename ...]-->
This directive allows you to insert content from a wiki page. If multiple pages are specified, the first that exists will be used.
<!--markup:wiki-markups-->
This directive allows the skin author to insert wiki markup like (:searchbox:). Keep it all on one line; no line-breaks! Avoid leading spaces too; something like <!--markup: (:tag:)--> can get treated as monospace markup (<pre>...</pre>) the same as using leading whitespace in a wiki page.
<!--function:FunctionName args-->
This directive allows you to insert content generated by a PHP function.
<!--file:/path/to/file-->
This directive allows you to insert content from a file.
<!--IncludeTemplate: {$Group}.tmpl default.tmpl-->
This directive allows the inclusion of another template file, see $SkinTemplateIncludeLevel.

The Page Title

Each HTML page requires a <title> tag. The content of it gets used in the heading of the results of search engines like Google. You may wish to include both the site title and the page title in your template.
Here is a basic solution:

<title>$WikiTitle - $Titlespaced</title>

One problem with this is that any (:notitle:) directive will not suppress $Titlespaced or $Title used that way in the Head.
Here is a solution which achieves that, by using a new variable in the template:

<title>$HTMLTitle</title>

Define the variable in the skin PHP file, and add a modified markup for the (:notitle:) directive:

global $HTMLTitle, $WikiTitle;
$HTMLTitle = $WikiTitle.' - '.PageVar($pagename, '$Titlespaced');

## Markup (:notitle:)
Markup('notitle','directives','/\\(:notitle:\\)/',
  "NoTitle2");
function NoTitle2() {
  global  $HTMLTitle, $WikiTitle;
  SetTmplDisplay('PageTitleFmt', 0);
  $HTMLTitle = $WikiTitle;
}

Sample basic templates

If all these elements are in place the resulting skin will be fully functional, but rather bare to look at. Here is a bare-bone skin template, with just a minimum of styling so the SideBar appears beside the page content. This is a table-less template that uses absolute positioning of the sidebar to the left side and a margin in the body tag to shift all other elements to the right.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>
<head>
  <title>$WikiTitle</title>
  <style type='text/css'><!--
    body { margin:1em 1em 0 14em; }
    #location { margin:0 0 8px 0; }
    #sidebar { position:absolute; top:10px; left:1em; width:10em; }
  --></style>
<!--HTMLHeader-->
</head>
<body>
<!--PageHeaderFmt-->
  <a href='$ScriptUrl/'><img src='$PageLogoUrl'
    alt='$WikiTitle' border='0' /></a>
<!--/PageHeaderFmt-->
  <hr />
<!--PageTitleFmt-->
  <h1 id='location'><a href='$ScriptUrl/$Group'>$Group</a>
  &raquo; $Title</h1>
<!--PageText-->
  <hr />
<!--PageFooterFmt-->
  <a href='$ScriptUrl/$[$Group/RecentChanges]'>$[Recent Changes]</a> |
  <a href='$PageUrl?action=print' target='_blank'>$[Printable View]</a> |
  <a href='$PageUrl?action=diff'>$[Page History]</a> |
  <a href='$PageUrl?action=edit'>$[Edit Page]</a><br />
  $[Page last modified on $LastModified]
<!--PageLeftFmt-->
  <div id='sidebar'><!--wiki:$Group.SideBar $SiteGroup.SideBar--></div>
<!--HTMLFooter-->
</body>
</html>

This skin template provides functionality and a basic page layout with the content ahead of the menu, which is advantageous for handheld browsers.

Here's another one, this time with a <!--PageActionsFmt--> section.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>
<head>
  <title>$WikiTitle</title>
  <style type='text/css'><!--
    body { margin:1em 1em 0 14em; }
    #location { margin:0 0 8px 0; }
    #sidebar { position:absolute; top:10px; left:1em; width:10em; }
    #actions
      { position:absolute; top:10px; right:1em; white-space:nowrap; }
    #actions ul { list-style:none; margin:0px; padding:0px; }
    #actions li { display:inline; margin:0px 5px; }
  --></style>
<!--HTMLHeader-->
</head>
<body>
<!--PageHeaderFmt-->
  <a href='$ScriptUrl/'><img src='$PageLogoUrl'
    alt='$WikiTitle' border='0' /></a>
<!--/PageHeaderFmt-->
  <hr />
<!--PageTitleFmt-->
  <h1 id='location'><a href='$ScriptUrl/$Group'>$Group</a>
  &raquo; $Title</h1>
<!--PageText-->
  <hr />
<!--PageLeftFmt-->
  <div id='sidebar'><!--wiki:$Group.SideBar $SiteGroup.SideBar--></div>
<!--PageActionsFmt-->
  <div id='actions'><!--wiki:$SiteGroup.PageActions--></div>
<!--PageFooterFmt-->
  <a href='$ScriptUrl/$[$Group/RecentChanges]'>$[Recent Changes]</a>
  (<a href='$ScriptUrl/$[$SiteGroup/AllRecentChanges]'>$[All]</a>)<br />
  $[Page last modified on $LastModified]
<!--HTMLFooter-->
</body>
</html>

Translation

The text inside $[ ] brackets (e.g. $[Page History], $[Recent Changes], etc.) will be processed by PmWiki's internationalization feature (see PmWiki.Internationalizations), and replaced by the appropriate text from the current XLPage.

CSS Files: Controlling Page Style

Page style can come from four places:

  1. Core PmWiki styles
  2. Recipe styles
  3. Skin styles
  4. Local stylesheets

A skin can provide styling three ways:

  1. A stylesheet linked from the skin.tmpl template file
  2. From skin.php using $HTMLStylesFmt
  3. A stylesheet linked from skin.php using $HTMLHeaderFmt

The main difference among these three is the order in which styles appear, which affects how skin styles take precedence compared to styles from the other sources. This is important because with CSS values assigned to attributes later will take precedence over what was assigned earlier. The last wins.

A single-stylesheet approach

Using this method, which is preferred by some very experienced skin authors, a single stylesheet is loaded via a $HTMLHeaderFmt definition in skin.php:

global $HTMLHeaderFmt;
$HTMLHeaderFmt['skin'] =
  "  <link rel='stylesheet' href='\$SkinDirUrl/skin.css' type='text/css' />\n  ";

The <link rel='stylesheet' ... /> line is omitted from the skin template entirely.

Here a hierarchy is created where where the skin styles override the core PmWiki styles and recipe styles from local configuration files such as config.php. The wiki administrator can take final control over styling using local stylesheets (but not recipes).

Of course multiple stylesheets may be loaded this way also, allowing logic to determine which stylesheet links appear late in the <head> section of the page. The point is that using $HTMLHeaderFmt in skin.php to link a stylesheet causes the load order of styles to be

  1. Core PmWiki styles and local/admin styles from recipes
  2. Skin stylesheet styles
  3. Local/admin styles from /pub/css/local.css, /pub/css/$Group.css, and /pub/css/$Group.$Name.css

Two-stylesheet approach

The default PmWiki skin, which has no skin.php file, uses a single stylesheet linked from the skin.tmpl file.

<link rel='stylesheet' href='$SkinDirUrl/skin.css' type='text/css' />
<!--HTMLHeader-->

The order of the two lines is very important. If they are reversed, the skin stylesheet link appears extremely late in the <head> section and the wiki administrator can't override the skin's styling. With the two lines in the correct order the load order of styles is

  1. Skin stylesheet styles
  2. Core PmWiki styles and local/admin styles from recipes
  3. Local/admin styles from /pub/css/local.css, /pub/css/$Group.css, and /pub/css/$Group.$Name.css

A skin author may, however, want to override the PmWiki defaults. The most reliable way to override the PmWiki defaults is to insert a second stylesheet using $HTMLHeaderFmt in skin.php, similar to the single-stylesheet method above.

global $HTMLHeaderFmt;
$HTMLHeaderFmt['skin'] =
  "  <link rel='stylesheet' href='\$SkinDirUrl/skin2.css' type='text/css' />\n  ";

By linking stylesheets from both skin.tmpl and skin.php, the skin author can allow some styles to be overridden by recipes, but not others. The load order of styles is

  1. Skin styles from a stylesheet linked from the skin's template (skin.tmpl)
  2. Core PmWiki styles and local/admin styles from recipes
  3. Skin styles from a stylesheet linked from skin.php using $HTMLHeaderFmt
  4. Local/admin styles from /pub/css/local.css, /pub/css/$Group.css, and /pub/css/$Group.$Name.css

Why pmwiki.php embeds CSS

The reason why pmwiki.php embeds the CSS is to reduce the overhead on skin authors and to preserve an upgrade path.

The styles defined by $HTMLStylesFmt['pmwiki'] are really the essential ones that are needed for several of PmWiki's core markups. Without these in place, a number of PmWiki's built-in features simply won't work.

In the past many have indicated that we should "dis-embed" the CSS from pmwiki.php, and require every skin author to include these minimal definitions (possibly modified) in the skin's .css file. This slightly increases the work required to develop a skin, because the skin author has to copy the definitions somewhere. But more importantly, moving all of PmWiki's CSS properties into the skins can make future PmWiki upgrades a bit of a pain, because when a style definition is added or changed in the core, every existing skin would then need to be modified to incorporate the new minimal specification. I prefer to avoid that level of coupling between core and skins.

The current approach allows skin CSS properties to be completely decoupled from CSS properties needed for core and recipe markups. In fact, in this sense pmwiki.php uses $HTMLStylesFmt[] in exactly the same way that recipes do. $HTMLStylesFmt[] allows module-specific CSS properties to be injected into the output without the skin ever having to be aware of them, while preserving the capability for skins and/or administrators to override the module-specific properties if they wish to do so.

Replacing default CSS entirely

A skin that wants to completely replace the pmwiki.php defaults with its own can do the following in skin.php:

global $HTMLStylesFmt;
$HTMLStylesFmt['pmwiki']    = '';
$HTMLStylesFmt['diff']      = '';
$HTMLStylesFmt['simuledit'] = '';
$HTMLStylesFmt['markup']    = '';
$HTMLStylesFmt['urlapprove']= '';
$HTMLStylesFmt['vardoc']    = '';
$HTMLStylesFmt['wikistyles']= '';

Bundling wiki pages

Starting with PmWiki 2.0.beta51 you can distribute skin-specific pages with your skin. What those pages might contain is limited only by your imagination.

Custom Edit Form and Preferences Page

You can use skin-specific pages to bundle a custom Edit Form and a Preferences page with your skin. Here's an example block of code from the Foo Skin's PHP script:

## Add a custom page storage location for bundled pages.
$PageStorePath = dirname(__FILE__)."/wikilib.d/\$FullName";
$where = count($WikiLibDirs);
if ($where>1) $where--;
array_splice($WikiLibDirs, $where, 0, array(new PageStore($PageStorePath)));

## Enable the Preferences page.
XLPage('foo', "$SiteGroup.FooXLPage");
array_splice($XLLangs, -1, 0, array_shift($XLLangs));

## Enable the skin's custom EditForm, either
## configurable via a prefs page (XLPage) or not.
if ($EnableEditFormPrefs == TRUE) {
  SDV($PageEditForm, "$[$SiteGroup.FooEditForm]");
} else {
  SDV($PageEditForm, "$SiteGroup.FooEditForm"); }
(note that $WikiLibDirs MUST be global for above code to work | Finar)

The customized edit page is pub/skins/foo/wikilib.d/Site.FooEditPage and the Preferences page is pub/skins/foo/wikilib.d/Site.FooXLPage, which contains the following wiki markup:

This is the Foo Skin Preferences page:

[=
  # Define a custom edit form.
  'Site.EditForm' => 'Site.FooEditForm',
=]

The Preferences page tells PmWiki to use the custom Edit Form, but it could also be used for other translations as well. The preferences page would also be a good place to put translation strings that are specific to the skin.

An admin who doesn't want users to create custom edit forms or other customizations can disable user preferences entirely by setting $EnablePrefs = 0.

JavaScript Tip

Note: This tip is slightly obsolete because it's now possible for form directives to specify "focus=1" as an option to indicate the control that should receive focus when the page is loaded.

"Favouring writers over readers": Putting the following code into the onload-handler (i.e. in window.onload = function(){ ... }) of the template gets the cursor focused on the first form element of the first form in the content area after the page is loaded, i.e. exactly where we want to have it (username field, edit field, etc.):

 var contentNode = document.getElementById("content");
 var contentForms = contentNode.getElementsByTagName("form");
 var firstContentForm = contentForms[0];
 if (firstContentForm) {
   for (var i=0; i < firstContentForm.length; i++) {
     if (firstContentForm.elements[i].type != "hidden") {
       firstContentForm.elements[i].focus();
       break;
     }
   }
 }

Enclose <!--PageText--> by an appropriate <div id="content"> clause for it to work.

Tested with NotSoSimpleSkin. ThomasP (PmWiki 2.1.beta25)

Notes

In references to skin.php above - skin can both be the literal 'skin' or the name of the skin in question, the latter takes precedence over the former. Chris Stiles

About <!--function:func param1 param2 ... : This will call the PHP function func with parameters param1, param2, ... and have the function's result inserted into the text. You can call any function that's defined by PHP, PmWiki, or an installed module/cookbook/skin recipe. (Warning: PmWiki functions may change name or functionality from release to release. Avoid calling them unless you have either foregone ever updating PmWiki, or asked in the mailing list whether using that function is OK. All functions might emit error messages if not called properly, most likely destroying your nice skin layout. You best bet is to write iron-clad functions in your skin.php that would degrade gracefully rather than let any error message out.)

See also

User notes? : If you use, used or reviewed "Skin Guidelines", you can add your name. These statistics appear in the Skins listings and will help newcomers browsing through the wiki.