As you know, an HTML page can link to one or more CSS stylesheets. Specifying what CSS stylesheets a page object should be linked to presents certain special challenges for RubyFrontier:
NOTE: These considerations apply equally to javascript files, which work almost exactly like stylesheets and are dealt with later on this page.
One HTML page can link to multiple stylesheets.
Order matters. The order in which stylesheet <link>
tags appear in an HTML page affects the cascade of styles that is ultimately used to determine the page’s appearance.
Different pages can share the same stylesheet. That’s part of the efficiency of linking to a stylesheet. So if page A and page B wish to differ in their styles, they can do it by linking to different sets of stylesheets, but not by linking to two different versions of the same stylesheet (because whatever version of a stylesheet is written to disk is the version that all pages linking to that stylesheet will share).
In the face of all this, RubyFrontier must provide a mechanism that allows you, the programmer, to specify for each page object what stylesheet(s) it should link to, in a manner consistent with the structure of the hierarchy and directives. This is not something that the original Frontier handled terribly well. My solution is as follows:
The basic directive for specifying stylesheets to be linked to is the scalar directive :linkstylesheets
. Its value is an array of stylesheet names (without the .css
suffix); so, for example, in a page object you might say #linkstylesheets %w{main secondary}
. Since this is a scalar directive, it can be encountered at any of the points where scalar directives are specified: in a #prefs.yaml
file as we walk up the hierarchy, in the page object itself, or in the template — in that order. The order is significant, because the rule for how this directive is processed is special; and here it is:
We maintain in the page table an array (called, appropriately enough, :linkstylesheets
). Every element of a :linkstylesheets
directive’s value is appended to the end of this array, in the order in which the elements appear, in the order in which the :linkstylesheets
directives are encountered.
In the above rule, there is no provision for overriding. But there needs to be a way, at a lower level of the hierarchy, to prevent a stylesheet specified at a higher level from being linked to at all. Therefore, there is another scalar directive :linkstylesheetsnot
, whose value is also an array of stylesheet names; when encountered, every element of its value is removed from the page table’s :linkstylesheets
array.
You can alternatively have a scalar directive of the form :stylesheetXXX
, where "XXX"
is the name of a stylesheet. (The name of the stylesheet is drawn from the key name, so the value is unimportant; by convention, it is usually true
.) This is treated precisely as if it had been :linkstylesheets
with a value of ["XXX"]
. This approach is deprecated, and exists solely for compatibility with the way stylesheets used to work in early versions of RubyFrontier.
The actual stylesheets are .css
files in a #stylesheets
folder. You can use as many #stylesheets
folders as you like throughout your site; just keep in mind that stylesheets are gathered up the hierarchy as the page is rendered, so the page being rendered must be able to “see” a #stylesheets
folder in order to use the stylesheets within it. And of course if there is a name conflict — that is, if there is more than one stylesheet with the same name in the #stylesheets
folders found upwards through the hierarchy — it is the first such stylesheet encountered that will be designated by that name. This arrangement allows you to maintain multiple #stylesheets
folders, each of which will become a folder in the rendered site. In the simplest and most common case, however, there will be just one #stylesheets
folder, located at the top level of the site where all pages can see it.
TextMate is good at editing .css
files, so maintaining your stylesheets should be no problem.
Late in the page rendering process, the page header is prefixed to the page. The page header will typically contain a call to the linkstylesheets()
standard macro. This macro fetches the :linkstylesheets
array from the page table and, for each element in turn, calls linkstylesheet()
with that element as parameter. The result is that (1) the stylesheet itself is written out to disk, into a stylesheets
folder at the top level of the site, and (2) a <link>
tag is inserted into the page’s <head>
region, linking to that stylesheet.
Thus, the order of the stylesheet names in the the :linkstylesheets
array is the order in which the <link>
tags are created. This solves the problem we set out to solve.
The page header can call linkstylesheet()
and supply a parameter value directly (a string representing the name of one stylesheet). The stylesheet is written to disk and a <link>
tag to it is substituted for the macro call. (As already mentioned, a linkstylesheets()
call is actually translated for you into a series of linkstylesheet()
calls.)
Another alternative is that you might want to embed a stylesheet in the page itself. To do so, the page header can call embedstylesheet()
. But this isn’t very flexible, so a more common approach is to set a scalar directive :embedstylesheet
(whose value is the name of the stylesheet) at the start of the page object; if you do so, the linkstylesheets()
standard macro in the page header calls embedstylesheet()
for you.
At some point, I discovered LESS, a more convenient way of writing and structuring CSS. I instantly added LESS support to RubyFrontier. This had to be done at the core RubyFrontier level because it affected two of the standard macros, linkstylesheet()
and embedstylesheet()
.
However, the developer of LESS stopped supporting it as a Ruby gem and turned it into a JavaScript thing. So I had to change the nature of RubyFrontier’s support. Initially, as a replacement for LESS, RubyFrontier started supported SASS instead. This meant modifying linkstylesheet()
and embedstylesheet()
again. Clearly, this was not going to be a maintainable approach going forward.
Ultimately, therefore, I just threw up my hands and added a filter, cssFilter
, which is used by the linkstylesheet()
and embedstylesheet()
macros. As with other filters, you are expected to put a file cssFilter.rb
in the #filters
folder; its top-level cssFilter
method is called the way filters are normally called, namely with one parameter — the page table. At that moment, the page table contains a :csstext
string entry consisting of the contents of the style sheet file now being processed by linkstylesheet()
or embedstylesheet()
; it’s up to you to modify this as desired.
So, for example, to run SASS against the stylesheet, you could say:
def cssFilter(adrPageTable)
adrPageTable[:csstext] = Sass::Engine.new(adrPageTable[:csstext],
:syntax => :scss, :style => :expanded).render
end
In that example, running SASS is unconditional; if you wanted to make it conditional, of course, you could. For example you might introduce a directive and check its value. To help you, the page table contains a :sheetName
string telling you the name of the stylesheet being processed.
To get you started, this approach to using SASS is illustrated both by the source for this documentation (RubyFrontier > Show RubyFrontier Docs Source) and by the new model site that you get when you create a new site (RubyFrontier > New Site).
JavaScript files are handled almost exactly like stylesheet files. So, the main points are precisely parallel to everything I’ve just said about stylesheets:
The page table maintains a :linkjavascripts
array of javascript file names. You can specify this value with the :linkjavascripts
directive, whose value is an array of javascript file names (without the .js
suffix; the actual javascript files, with the .js
suffix, should live in a #javascripts
folder). The :linkjavascripts
directive can appear in various places, so different parts of your site (such as the #prefs.yaml
file and the current page object) can both contribute to the :linkjavascripts
array. You can alternatively use a scalar directive of the form :javascriptXXX
, where "XXX"
is the name of a JavaScript file.
You can use as many #javascripts
folders as you like throughout your site; their contents are gathered and folded at the start of the page rendering process, and each will become a folder in your rendered site. The simplest case would be just one #javascripts
folder, located at the top level of the site where all pages can see it.
The page header will typically contain a call to the linkjavascripts()
standard macro. This macro fetches the :linkjavascripts
array from the page table and, for each element in turn, calls linkjavascript()
with that element as parameter. The result is that (1) the javascript file itself is written out to disk, into a javascripts
folder at the top level of the site, and (2) a <script>
tag is inserted into the page’s <head>
region, linking to that javascript file.
Alternatively, the page header can call linkjavascript()
and supply a parameter value directly (a string representing the name of one javascript file).
To embed a javascript in the page itself, the page header can call embedjavascript()
, but a more common approach is to set a scalar directive :embedjavascript
(whose value is the name of the javascript) and let the page header’s linkjavascripts()
call it for you.
Both CSS and JavaScript files are passed through ERB as they are processed. Essentially, this means they are macro-processed, except that the snazzy name-resolution of full-fledged macro-processing is not present. But the current context is the PageMaker object, so its methods are available, and the page table is available (as adrPageTable
). Moreover, if the stylesheet or JavaScript file is being linked to (as opposed to being embedded), its file URL within the Web site folder is available as adrPageTable[:sheetLoc]
.
The purpose of this feature (introduced in RubyFrontier 0.9.9.6) is to make it easier for a CSS or JavaScript file to link to an image file, or similar, via a relative URL. For a full example, see the page discussing images.
This documentation prepared
by Matt Neuburg, phd = matt at tidbits dot com
(http://www.apeth.net/matt/),
using RubyFrontier.
Download RubyFrontier from
GitHub.