This page uses CSS, and renders properly in the latest versions of Microsoft Internet Explorer and Mozilla.
This is Frontier: The Definitive Guide by Matt Neuburg, unabridged and unaltered from the January 1998 printing.
It deals with the freeware Frontier 4.2.3; for commercial Frontier 8, look here, and for the inexpensive Radio UserLand, look here. See my Web site for information.
Those wishing an offline or printed copy may purchase the book, which is still in print.
Copyright 1998 O'Reilly & Associates, Inc. ALL RIGHTS RESERVED. Reprinted with permission.
TOP UP: Applied Frontier NEXT: Dynamic Web Sites

41

Web Site Management

A Web site is a collection of Web documents (HTML, graphics, and so forth) that you maintain for people to look at over the Internet. You might have your own server, or you might upload documents to a remote server; it makes no difference. Frontier is ideally suited, not just for making an individual Web page, but for creating and maintaining an entire Web site which might consist of many, many pages.

To see why, consider this: HTML is just text - text that's tedious for humans to deal with directly, but easy for a computer to help generate. Furthermore, the different pages of a Web site often need certain things in common to make for a pleasant, efficient browsing experience. Pages may want a similar look. Navigation might require "Next" and "Previous" links to guide the reader through the various pages. There might be a page which outlines the entire site, with links to the other pages, functioning as a hypertext table of contents. Frontier can generate text files computationally, so it can easily take care of these sorts of details.

In this chapter, we describe Frontier's Web site management facilities. These are implemented mostly by the html suite, which uses menu items and the database itself as its user interface. At the end of the chapter, we describe an alternative user interface which allows you to take advantage of Frontier's Web site management facilities through BBEdit and menu sharing.

Frontier's Web site management facilities are powerful, flexible, and generous. Whatever your needs, not only is there probably a way to do it with Frontier, but there is probably more than one way. Suggestions and examples given in this chapter are often not the only means of achieving the desired effect.

The Frontier Web site management facilities also provide lots of room for you to add to them and adapt them by writing scripts of your own. In learning about Frontier Web site management, it is easiest to start by building a simple site and confining oneself to the many features offered by Frontier directly "out of the box."1 Once familiar with these, one can begin writing scripts that adapt the system to achieve special effects. UserLand also makes available the source code of many of its own Web sites; you can learn from and borrow this code. And there are Frontier mailing lists and Web sites where users share code that they have written to achieve particular effects.

In the unlikely event that the html suite cannot meet one's special needs, it is possible to make a copy of it and modify the code at a deeper level; Frontier is an open system, after all, and the html suite is just a suite like any other.

Architecture

The architecture presupposed by the html suite is that the raw material for the site's pages, including any graphic images, is stored in the database. Frontier will then computationally produce from this the actual HTML files and graphic files that make up the site. This is called rendering the site.

Under this architecture, the terms "Web site" and "Web page" become ambiguous. A Web site can be a table in the database, or the folder on disk into which the site is rendered. A Web page can be a database object in a Web site table, or the file that is created from it when the object is rendered. I will speak of the objects in the database as Web site tables and Web page objects, which represent the folders and files that are created when the site is rendered.

The part of the html suite that actually processes Web page objects to turn them into Web page files is termed the rendering engine. The rendering engine performs various transformations upon a Web page object in order to produce a Web page file, but it does not actually change the Web page object in the database; only the user will do that. A Web page object is like a set of instructions from the user on how to make a Web page file; clearly, Frontier must not alter the instructions. In this chapter I may sometimes say that the rendering engine does this or that to a Web page object, but I don't mean that it literally changes the object itself: I mean that it transforms a temporary copy of the object, in the course of producing the Web page file.

Keeping the site material in the database takes advantage of the fact that database objects are Frontier's natural domain, and lets the rendering engine derive meaning from such native features as the hierarchy of subtables. Even more important, it is as easy for Frontier to generate a very large number of files as to generate one, whereas the user's work is kept to a minimum because in the database are instructions for making the Web site; a small change in those instructions is enough to change the whole site. Suppose, for example, that for an existing site, it is decided that a certain logo should appear at the top of every page. Instead of editing each page to insert an <img> tag, the user introduces one <img> tag in one database object, and has Frontier re-render all the pages.

Web Site Tables

A Web site table should be located inside user.websites, and should be created by choosing the New Site menu item from the Web menu, which makes a new table and inserts certain important components into it.2 A Web site table may contain the following sorts of entries:

  • A subtable called #ftpSite .3 The entire site should contain exactly one of these, in the table representing the site's root level; in fact, its presence actually means, "this is the site's root level." It should have at least these three entries, which must be configured by the user:

isLocal

Should initially be true. If false, Frontier will render pages and upload them with an FTP client to a remote site, all in one motion; this is discussed in "Previewing and Releasing," later in this chapter.

folder

The pathname of a folder on disk, into which the site is to be rendered. Neither the folder nor any of its containing folders need exist (though of course the volume must); the rendering process will create them if necessary.

url

The URL of the remote directory from which the files and folders in folder will be served up over the Net. This value is of diminished importance and for many purposes can be left unset without penalty. If you set it, do not include "http://", and do add a final slash.
  • An entry called #template . This is typically a wptext or outline.4 Web page objects are embedded into their templates before being written out to disk as files; this embedding is how opening and closing HTML, and header and footer elements common to several pages, are attached. Templates are explained under "Templates," later in this chapter.
  • An entry called #filters. This is typically a table5 containing two scripts named pageFilter and finalFilter. The use of these scripts is explained under "Filters," later in this chapter.
  • Any other entries whose names begin with #. These, along with those already listed, are directives. Directives set values which will be used during rendering. Many directive names have special meaning: the rendering engine looks specifically to see what value you have assigned them, if any, so that it knows how you want it to behave. You are also free to make up directive names of your own choosing; these are like variables to which you can refer within page objects. Directives are explained under "Directives," later in this chapter.
  • A subtable called glossary (or, optionally, #glossary). This is a table of name-value pairs for substitution at rendering time; its use is explained under "Glossaries," later in this chapter.
  • A subtable called tools (or, optionally, #tools). This may contain anything at all, such as utility scripts associated with the site table, and is a convenient hiding place for objects which might otherwise be mistaken for Web page objects.
  • A subtable called images . This is for binaries of graphic images referred to by pages in the site; see "Macros," later in this chapter, on the imageRef() script.
  • Any other wptexts, outlines, strings, scripts, fileSpecs, or addresses, whose names don't begin with #. These are the Web page objects representing the HTML files that constitute the site. The name of the entry determines the name of the file; the suffix ".html" (or any other desired suffix) will be added during rendering, and should not be part of the entry name. The treatment of these entries is explained under "Web Page Object Datatypes," later in this chapter.
  • Any other subtables whose names don't begin with #. These represent subfolders of the Web site folder; the name of the subtable determines the name of the folder. These subtables may contain any of the elements listed here, except an #ftpsite entry. Web page objects are generally grouped by table so as to apply directives to them in common, as explained in the next section, "Directives."

The table user.websites is itself a Web site table, and may be examined to see some characteristic Web site table entries.

Directives

A directive is a user-defined name-value pair.6 Frontier knows that you are defining a directive because the directive name is preceded by # . So, for example, if a Web site table contains an entry named #link whose value is the string "0000FF", then Frontier responds to the # by associating a name link with a value "0000FF". The association takes place in a special table, html.data.page, which is used as a scratchpad during rendering; so in this case Frontier will create, during rendering, an entry html.data.page.link whose value is "0000FF".

Defining Directives

A directive may be defined, as we have just seen, by making it a Web site table entry, in which case the name of the entry tells the name of the directive (preceded by #) and the value of the entry tells the value of the directive.

Alternatively, a directive may be defined within a Web page object that is a wptext or an outline. To do this, a paragraph (or, in an outline, a summit-level line) must begin with # followed by the directive name and then a space; everything in the paragraph (or summit-level line) after this space is the value of the directive. The rendering engine will remove these directive definitions after obeying them (so that they don't show up in the final HTML of the Web page object).

Here is how the rendering engine obeys directives in a wptext or outline: it constructs a UserTalk assignment statement, placing the directive's value on the right of the equals-sign; the statement is then evaluated. For example, if a paragraph in a wptext Web page object says:

#link "0000FF"

then the rendering engine will construct a string:

"html.data.page.link = \"0000FF\""

and evaluate it. So what comes after the space must be something that can go on the right side of a valid UserTalk assignment statement. Moreover, it can be anything that can go there. For example, one might say:

#link blue

or:

#link workspace.calculateMyColor( )

Thus, there are two important points of difference between the directive value as assigned through an entry in a site table and the directive value as assigned within a wptext or outline object.

First: in a table, the datatype of an entry is fixed beforehand; you can see it in the Kind column. But in a wptext or outline the datatype of the value will depend on the rules of UserTalk as the assignment statement is evaluated. That's why, when we say:

#link "0000FF"

the quotation marks are needed around "0000FF", to express a string literal when the rendering engine constructs the assignment statement.

Second: in a table, the value of an entry is fixed beforehand; you can see it in the Value column. But in a wptext or outline, the evaluation takes place at rendering time. For example, when we say:

#link workspace.calculateMyColor( )

the rendering engine will assign to html.data.page.link whatever is the result of calling workspace.calculateMyColor() at the particular moment of rendering.

Directives with large string values

Assigning a directive a lengthy string value can be tricky. That's because there is a limit of 255 characters on the length of a string literal. But a string can be any length, so one solution is to build the string out of shorter literals, like this:

#meta "a very long string" + "another very long string"

A possibly more convenient solution is to have the value be a wptext (which can be as long as you like) coerced to a string:

#meta string(user.websites.mySite.tools.myMetaWptext)

This problem arises for very few directives, but when it does, it's nice to know what to do.

The Directive Hierarchy

The chief reason for defining a directive in one location rather than another is that there is a directive hierarchy, as follows:

  • A directive defined in user.html.prefs applies to every Web page object you ever render. This table is a kind of global directive repository. Directives in this table should not have # before their name!
  • A directive defined in a Web site table applies to every Web page object in that table or in a subtable of that table.
  • A directive defined in a Web page object applies to that page alone.

Where the same directive is defined in more than one place, the one "nearest" the page object being rendered is the one that applies. In effect, for any given directive, we look for its value first in the page object itself, then in the table containing the page object, then in the table containing that table, and so on up to the root; and then in user.html.prefs.7

The directive hierarchy enables a sort of object-oriented system of inheritance and override. If a directive is defined in a table, all the page objects and subtables in that table inherit the definition, though each of them can override it. This is what I meant earlier when I said that "Web page objects are generally grouped by table so as to apply directives to them in common." The organization of a Web site's folders on disk is irrelevant to the experience of the human reader browsing the site; it is the links within the pages that organize the site for the reader. So the organization of Web page objects in tables (and consequently folders) is available for this use.

Special tables that are really directives are variously handled. For example, the tools table is treated like an ordinary directive: the tools table "nearest" the page object being rendered is the one automatically associated with that page object. But entries in glossary tables are sought in every glossary table all the way up the table hierarchy. Exceptional handling, like that of the glossary tables, is noted later.

A Directory of Directives

Here is a reference list of the chief directives consulted during the rendering process. Some of these will be more comprehensible when you have read the rest of this chapter. Nevertheless, you should probably read through the list now, as a study of the directives is a good way to learn what sort of thing the rendering engine does for you.

Unless otherwise noted, all directives default to true if not defined somewhere in the hierarchy. Some directives are automatically given defaults in user.html.prefs; those defaults are not listed here (consult the table, or html.init()).

Header directives

The template typically begins with a macro that calls the pageHeader() script (in html.data.standardMacros). This script constructs the opening HTML for the page, including the <head>...</head> region. The following directives correspond to material that goes in this region.

#title

The string value to appear between the <title> tags. The default is the name of the page object.

Most browsers use this value in the titlebar of the window.

It would be silly to assign a #title directive anywhere but in a page object, because every page object should have a unique #title - other features of the Web site management system use the #title to identify a page. Nevertheless, I like to assign user.websites.#title the value "Hey, NO TITLE!!!!!!"; that way, if I forget to assign a page object a title, I see this when I view the rendered object in the browser, and am reminded.

#includeMetaCharset

A boolean. If true, a <meta> tag is inserted saying what character set we are using.8 The name of the character set comes from another directive, #charset, which is set in user.html.prefs as "iso-8859-1".

#meta

The entire value is inserted in the <head> area of the page. Optional.

Despite its name, you can use #meta to put anything you like into the <head> area, not merely a <meta> tag; nothing is prefixed or appended to the value of the literal when it is inserted (except a return character afterwards), so forming a tag is up to you. You only get one #meta directive per page (if you define two, the second will override the first), so if you have several tags to insert, they all need to go into this one directive value. See "Directives with large string values," earlier in this chapter.

#javascript

The value is surrounded with <script> tags and comment delimiters, and is inserted into the <head> area. Optional.

See "Directives with large string values," earlier in this chapter. The surrounding HTML is taken care of for you, so the value need be only the actual JavaScript script. However, you should precede any left curly braces, and perhaps any left double quotation marks, by a backslash, to prevent the rendering engine from seeing these as macros and glossary entries, respectively (as explained later).

Body tag directives

The template typically begins with a macro that calls the pageHeader() script (in html.data.standardMacros). This script constructs the opening HTML for the page, including the <body> tag.9 It looks for the following directives, all optional, corresponding to parameters of the <body> tag.

#link

The hex color value for links in the page

#alink

The hex color value for a link in the page as the user clicks it

#vlink

The hex color value for links in the page that have been visited

#bgcolor

The hex color value for the page background

#text

The hex color value for text in the page

To type a hex color value as the value of a string object in a table, it may help to include the surrounding double-quotes. Or, you can use any of the predefined constant names in system.verbs.colors.

#background

The URL for an image file to be used for tiling the page's background. The use of this is further described under "Some Utility Scripts," later in this chapter.
Footer directives

The template typically ends with a macro that calls the pageFooter() script (in html.data.standardMacros). This script constructs the closing HTML for page, including the </body> and </html> tags. It looks for the following directives; both are optional, and if the first is true the second is not looked for.

#noHintsInHeader

A boolean. If false, suites.fatpages.buildPageAtts() is called to generate a machine-readable "hint" comment at the bottom of the page, after the closing </html> tag.10 The fields of the hint can be retrieved from the final HTML into a table with fatpages.getPageAtts().

#adrPageData

Address of a database object to be encoded into the hint. The presence of such a database object within an HTML comment makes the Web page a "fatPage." Since HTML is a cross-platform network-servable format, a fatPage Web page is a way to distribute small database objects to other users. Since the exported object is inside a comment, it is invisible to the browser, so your Web page needs to state explicitly that it is a fatPage if readers are to know about it.11
HTML generation directives

On the whole, the rendering engine is extremely conservative about generating HTML. Frontier is not trying to save you from knowing HTML; it is assumed that you have created most of the HTML yourself, by typing or with BBEdit or Nisus Writer or some similar external editor, or indirectly with a WYSIWYG tool. However, a few extremely common types of HTML are taken care of for you if you wish.

Note that if a Web page object is an outline, its outline structure can also be used to generate HTML. See "Outline Renderers," later in this chapter.

#autoParagraphs

A boolean. If true, any instance of two successive return characters will have a <p> tag inserted just before it.

Trying to combine autoParagraphs mode with manual insertion of your own <p> and </p> tags in a single page can result in some pretty skanky HTML, and is not recommended.

#clayCompatibility

A boolean.12 If true, any paragraph beginning with *** will have the *** removed and will be surrounded with <b> tags. Also, any paragraph consisting entirely of --- is converted to <hr>.

#activeURLs

A boolean. If true, any "word" containing @ or // will be assumed to be a URL, and will be surrounded with <a> tags to make it a link.

So, for example, if activeURLs is true and a wptext Web page object contains the following:

<p>Write me at matt@tidbits.com.</p>

<p>Visit http://www.scripting.com.</p>

then the rendered page will contain this:

<p>Write me at <a href="mailto:matt@tidbits.com"> matt@tidbits.com</a>.</p>

<p>Visit <a href="http://www.scripting.com"> http://www.scripting.com</a>.</p>

If activeURLs is true you can still prevent any particular occurrence of @ or // from generating a link by preceding it with a backslash. See "Escape Character," later in this chapter.

#isoFilter

A boolean. If true, the page is passed through string.iso8859encode() to translate high-ASCII characters; for details, see Chapter 14, Strings and Chars.
Rendering engine directives

The rendering engine performs various standard tasks (all described in the course of this chapter). The user can opt out of any of these with directives, but will not typically wish to do so; the usual set of choices is that #directivesOnlyAtBeginning, #tagSubstitution, #processMacros, #expandGlossaryItems, and #useGlossPatcher will all be true, and threadedRendering will be false unless a CGI or other threaded mechanism might render a page.

#directivesOnlyAtBeginning

A boolean. If true, the rendering engine will stop looking for directives within any wptext Web page object as soon as it encounters a paragraph that doesn't begin with #. Clumping all directives at the top of the wptext and setting this directive to true can save time during rendering.

#tagSubstitution

A boolean. If true, the rendering engine will perform its default substitutions for the template pseudo-tags <title> and <bodytext>. See "Templates," later in this chapter.

#defaultTemplate

The string name of an entry in user.html.templates to be used as the template if a page object lacks an associated template. Defaults to "normal". See "Templates," later in this chapter.

#processMacros

A boolean. If true, the rendering engine will evaluate expressions in curly braces as UserTalk expressions and substitute the result for the expression (unless the left curly brace is preceded by a backslash). See "Macros," later in this chapter.

#expandGlossaryItems

A boolean. If true, the rendering engine will treat expressions in double quotation marks as invocations of glossary items and will attempt to replace them with the value of those glossary items (unless the first double quotation mark is preceded by a backslash). See "Glossaries," later in this chapter.

#useGlossPatcher

A boolean. If true, the rendering engine will resolve glossPatch expressions to relative links. See "Relative Links," later in this chapter.

#threadedRendering

A boolean; can be set only in user.html.prefs, and must not be undefined. If true, the rendering engine will raise a semaphore before first using html.data.page, and lower it when finished. Should be set to true if there is a possibility that Web page objects might be rendered at the behest of more than one thread simultaneously (e.g., by a CGI script).

#renderOutlineWith

Meaningful only when the Web page object being rendered is an outline. Designates a script object which will be called to interpret the page object's outline structure to generate HTML. See "Outline Renderers," later in this chapter.
File directives

These settings mostly determine details about rendered files as files.

#textFileCreator

The string4 creator code for rendered HTML files. Frequently, a browser's creator code is used.

#imgFileCreator

The string4 creator code for rendered image files. Frequently, a browser's creator code is used.

#defaultFileName

The string name (minus any suffix) that the server supplies when a URL specifies no filename. Typically, "default" (the default) or "index".

#fileExtension

The suffix to be added to the names of Web page objects to form the name of the file to which they will be rendered. Typically, ".html" (the default) or ".htm".

#dropNonAlphas

A boolean. Determines how a filename is formed from the name of a Web page object: if true, toys.dropNonAlphas() is called to remove nonstandard characters.

#lowerCaseFileNames

A boolean. Determines how a filename is formed from the name of a Web page object: if true, string.lower() is called to make the name lowercase.

#maxFileNameLength

A number. Determines how a filename is formed from the name of a Web page object: a long entry name will be truncated, so that when fileExtension is appended the full filename will not exceed maxFileNameLength. The default is 31.

#fullTimeNetConnection

A boolean. If true, then when rendering and uploading to a remote site in one operation, the rendering engine will talk to the remote site more frequently. (For example, when a page object is rendered and uploaded, the browser will display the remote version rather than the local version.)

Outline Renderers

When a Web page object is an outline, the rendering engine can use its outline structure to generate HTML. This is can be a great convenience, as it is much easier to arrange an outline than it is to write correct HTML by hand.

How the engine converts outline structure to HTML depends upon the value of the directive #renderOutlineWith (see "Directives," earlier in this chapter). If the directive has no value for a particular outline page object, the object is rendered with the default outline renderer, which results in nested unordered HTML lists. Otherwise, the outline renderer script named by #renderOutlineWith is called to generate the HTML. (Therefore, if you define a #renderOut-lineWith in a table, it is impossible to specify the default outline renderer for a page object anywhere in that table.)

The value of #renderOutlineWith should be a string, which the rendering engine will try to use as follows:

  • 1. The string is coerced to an address and dereferenced to get an object reference.
  • 2. If that doesn't work (there is no such object), the string is taken to be the name of an entry in the tools table associated with this page object.
  • 3. If that doesn't work (there is no such entry), the string is taken to be the name of an entry in user.html.renderers .

The user is free to create and store outline renderers in any of these locations, and some are included already.

Frontier ships with a number of sample outline renderer scripts, in user.html. renderers. You can learn what they do by studying the scripts, and by rendering an outline Web page object with one of them (see "Previewing and Releasing," later in this chapter). Here is a quick rundown:

newCulture

Levels are undifferentiated. Blank lines have a <p> inserted, and the outline is coerced to a string. This permits the organizational convenience of an outline with minimal effect on the resulting HTML.

twoLevelOutline

Level 0 lines become <h4> paragraphs; lines at all other levels are undifferentiated and become <p> paragraphs.

prettyOutline

Level 0 lines become <h3> paragraphs capitalized with a horizontal rule above. Level 1 lines become <h4> paragraphs, numbered. Other levels are undifferentiated, becoming block-quoted paragraphs.

siteOutliner

Levels are reflected as nested unordered HTML lists, as with the default outliner. The difference is this: if a line contains a comma, what precedes it becomes the basis for a relative URL to turn what follows it into a live link.

daveNetOutline

The whole outline becomes a two-column table: level 0 lines go into the first column, lines at all other levels go undifferentiated into the second column as individual paragraphs. Everything gets <h4> formatting.

tableOutliner

Each level 0 line plus any level 1 lines subordinate to it become cells in one row of a table. Deeper level lines are ignored. The first row of the table is bolded. Optional directives permit some further control:

#border

The value for the <table> tag's border parameter

#width

The value for the <table> tag's width parameter

#cellpadding

The value for the <table> tag's cellpadding parameter

#centeredCols

A list of the numbers of those columns that are to be centered

cadillac

Every line becomes a paragraph whose left margin is indented an amount proportional to the line's level (accomplished through HTML tables); the resulting page looks like the outline. Level 0 lines are bolded.13

The included renderers are good examples for study, to learn to write your own; it's easy.14 The outline renderer script is called with one parameter, the address of an outline object. By the time the renderer is called, the outline's directives and summit-level comments have been removed, and the outline is the target, so you can navigate using op verbs immediately. The outline is a copy, so it may be freely modified.

A string must be returned, and there are three typical strategies for deriving it:

  • Gather the lines of the outline into a string, editing them as you go, and return the string, like tableOutliner.
  • Edit the outline in place, and coerce it to a string and return the string, like newCulture.
  • Edit the outline in place; then send it to html.ucmds.getOutlineHtml() to insert some HTML, and return the result, like siteOutliner.

html.ucmds.getOutlineHtml() takes five arguments - a pointer to the outline, and these:

  • a. text to insert before the first line, and before every line indented with respect to the previous line
  • b. text to insert after the last line, and after every line indented with respect to the following line
  • c. text to precede every line
  • d. text to follow every line

(c) and (d) are tightest to the line; (a) and (b) go outside of them, like this: ac xxx db. Nothing is added that you don't explicitly add (such as return characters).

Templates

A template is a wptext or outline object which acts as a framework into which a Web page object is embedded as part of the rendering process. A typical template provides at least the opening HTML (the <html> tag, the <head> area, and the <body> tag) and the closing HTML (the </body> and </html> tags), with the Web page object embedded between them; thus, Web page objects typically consist only of the inside of the <body> area.

Multiple Web page objects can share the same template (usually by means of the directive hierarchy). Thus, a frequent use of a template is to provide the beginning and/or end of the inside of the <body> area. All Web pages rendered with that template will have certain material in common, the material provided by the template; the Web page object itself consists of what is different for each Web page. The site as a whole thus ends up with a consistent look.

Furthermore, there are ways to make even the part of a Web page that is provided by the template vary from page to page. An example appears in Figure 1-3; the template has put three links at the start of every page; the links are stylistically identical on all the pages, but they are different links on each page. This is typical of the power of templates. (In "Some Utility Scripts," later in this chapter, we shall see how this particular effect was accomplished.)

Every Web page object must be associated with a template. This association is performed by defining a #template directive; the object-template association thus takes advantage of the directive hierarchy. Failure to associate a page object with a template will result in the template named by #defaultTemplate being used; by default, this is "normal" (meaning user.html.templates.normal).

There are two typical modes of #template definition:

  • The #template definition is a table entry. In this case, its value is usually the actual wptext or outline object; or it can be the string name of a wptext or outline in user.html.templates.
  • The #template definition is inside a wptext or outline Web page object. In this case, its value is usually the string name of a wptext or outline in user.html.templates . However, it is also possible, by a kind of trick, to associate a page object with a template anywhere in the database, using this syntax:

#template user.websites.tools.mySpecialTemplate

#indirectTemplate false

  • The details, if you're going to use this trick, are important: the #template value must be a database object reference, and the #indirectTemplate definition must follow immediately.

By default, a Web page object is embedded by substituting it for the pseudo-tag <bodytext> which appears in its associated template.15 A template may also contain a pseudo-tag <title> ; the value of the #title directive will be substituted for it. Both pseudo-tags may be used only in templates. The two pseudo-tag substitutions take place only if the directive #tagSubstitution is true, which it normally should be. It is possible to obtain other embedding arrangements through the use of custom directives (see "Macros," later in this chapter), but this is not commonly necessary.16

The minimal template typically consists of:

  • A call to html.data.standardMacros.pageHeader() to generate the opening HTML
  • The <bodytext> pseudo-tag, to represent the embedded Web page object
  • A call to html.data.standardMacros.pageFooter() to generate the closing HTML

The verb calls take place through macros, as explained under "Macros," later in this chapter.

Templates may contain directives. These are handled after directives in the embedded Web page object, so if the same directive is defined both in a Web page object and in its associated template, the template definition will override - unless the directive has already been obeyed earlier in the rendering process. See "Important Routines," later in this chapter, to get a picture of the order of events during rendering.

A template can be either a wptext or an outline. The chief reasons for making a template an outline instead of a wptext are organizational convenience and legibility. It makes no significant difference to the resulting HTML; the outline is coerced directly to a string before the Web page object is embedded (it is not passed through an outline renderer script). Also, making a template an outline lets you take advantage of the #define and #defineScript directives (see "Macros," later in this chapter).

If you wish to avoid the template mechanism altogether (i.e., a Web page object is to contain or generate all of its HTML, deriving nothing from the template), have the template consist of the single pseudo-tag <bodytext> and nothing else; see the template at user.html.templates.bbedit for an example.

Web Page Object Datatypes

Here are the datatypes that a Web page object may consist of, and how they are handled by the rendering engine:

An address

The object pointed to becomes the Web page object. That object must be one of the types listed here. (It can even be another address!)

A table

The table is rendered into an HTML table, imitating a table edit window, with Name, Value, and Kind columns; directives within the table are not handled. The way to use this feature is to have an object in your Web site table be an address pointing to a table, or to include the table in another Web page object with renderObject() (see the next section, "Macros"); that's because a table inside a Web site table will be understood by the rendering engine as representing a folder, not a Web page.

A script

The script is called, with no parameters; the result is coerced to a string. Directives in this string are not handled, but the script can define directives before returning its result, by inserting them into html.data.page directly.

A string, a wptext, or a filespec

If a fileSpec, the file must be of type 'TEXT' (or an alias to such a file); the file is read in as a string. If a wptext, it is coerced to a string. Directives in the string are handled.

An outline

Directives in the outline are handled, and summit-level comments are deleted. The outline is then handed to the outline renderer script specified by the #renderOutlineWith directive; if no such directive is in effect, it goes to the default outline renderer. See "Outline Renderers," earlier in this chapter.

Macros

A macro is a UserTalk expression in a Web page object; during rendering, the expression is evaluated, and the result substituted for the original expression. Typically, the expression is either a database object reference or a verb call.

A macro provides a way to calculate a stretch of text inside the Web page. Often, the point will be that the text is calculated under the particular conditions at rendering time, such as what page this is, what time it is, what value a certain directive has, and so forth. Also, a macro can be a verb call where the verb's action, not its result, is what's important; if the verb returns the empty string, the original macro will simply be deleted during rendering, with nothing substituted for it.

A macro consists of a UserTalk expression surrounded by curly braces ({}). The curly braces are the sign to the rendering engine that this is a macro. A macro may appear in a template or in a Web page object; evaluation takes place after the page object has been embedded in the template. Whether macros are evaluated for a particular template-object pair depends upon the value of the directive #processMacros.

Even if #processMacros is true, an individual instance of curly braces can be exempted from evaluation by preceding the left curly brace with a backslash; this can be important to prevent errors when literal curly braces are intended. Material within double quotes or angle brackets is assumed to be a literal string or an HTML tag, respectively, and is protected from macro evaluation; this protection can be turned off by preceding the left double quote or left angle bracket with a backslash. See "Escape Character," later in this chapter.

The UserTalk expression is evaluated inside a set of nested withs so that any object references it contains are sought as follows:

  • 1. Inside the tools table associated with the Web page object.
  • 2. Inside user.html.macros . This provides a global library of user-created scripts and values that macros can draw upon.
  • 3. Inside html.data.standardMacros . This table belongs to UserLand and should not be altered; its scripts are important, though, and are very commonly called in macros. They are discussed under "Standard Macros," later in this chapter.
  • 4. Inside html.data.page . The name-value pairs for defined directives are entries here, so it is possible for a macro to pick up directive values simply by referring to them by name.
  • 5. In the database as a whole.

If the expression cannot be evaluated as a legal UserTalk expression in this way, an error message is substituted for it in the rendered HTML.

Custom Directives

Recall that macros are not evaluated until after the Web page object has been embedded in its template; by that time, all directives have been handled. A macro can refer to a directive by name; the directive is an entry in html.data.page, and macros are evaluated in a nest of withs which includes html.data.page, so the value of the directive will be substituted for the name.

For example, the rendering engine creates a #url directive, whose value is the URL for the current page; to mention this URL within the page, you could say in the page object:

{url}

No law says that directives are confined only to those with names that are meaningful to the rendering engine; you are free to define a directive with any name. This means that you can insert a name-value pair into html.data.page with a directive definition, and retrieve the value later with a macro. This mechanism, the custom directive mechanism, is like having temporary variables during the rendering of a Web page object.

A common use for this mechanism is to hand information from a Web page object to its template. Suppose, for example, that every page is to start with a title, different from its #title, in large colored letters. So, we have some formatting which is to be true of every page, and which is to appear at the beginning of the page; it makes sense, therefore, to have the title be part of the template. Such an arrangement avoids repeating the same HTML in every page; and if we decide to change the formatting of the title, we change the template once and re-render the site. On the other hand, we have a problem: the text of the title is different for each page.

A custom directive is the answer. We might have this in the template, just before the <bodytext> pseudo-tag:

<center>

<h1><font size="7" color="#cc9900">{myTitle}</font></h1>

</center>

Thus the template sees to the presence and the formatting of the title. Then, in each page object to be rendered with that template, we have a directive definition giving the title's text:

#myTitle "History of the Universe"

(or whatever the page's particular title might be).

It is also possible to use a directive value inside an HTML tag, but this calls for some trickery because material inside HTML tags is protected from macro evaluation. Suppose we wanted each Web page object to hand up to the template not only the text of the title but its color as well:

#myTitle "History of the Universe"

#myTitleColor "#cc9900"

The template could contain a macro which constructs the HTML for the <font> tag as a UserTalk expression:

<center>

<h1>{"<font size=\"7\" color = \"" + myTitleColor + "\">"}

{myTitle}

</font></h1>

</center>

Special Outline Directives

Web page objects that are outlines, and templates that are outlines, can employ two extra directive-defining directives. The reason that only outlines can do this is that these directives take advantage of the outline structure as part of their syntax.

The directives are called #defineScript and #define. Each takes as its value a string that is to be the name of the directive; the bundle subordinate to the definition line is copied into a script or outline object, respectively, to become the value of the directive. The idea is that you can then call the script, or retrieve the outline, by way of a macro or a directive definition.

For example, this is how one of my outline Web page objects establishes its <meta> tag:

#define "myMeta"

<META name="description" content="Free stuff for learning Frontier">

<META name="keywords" content="Frontier, UserLand, Neuburg, Scripting">

#meta string(html.data.page.myMeta)

Except that in my actual page, each content value is much longer; the use of #define thus lets me feed the #meta directive a string which exceeds the 255-character limit on string literals and outline lines.

Standard Macros

Recall that macros are evaluated in a with so that entries in html.data.standardMacros can be referred to by name alone. The following are some of the scripts included in html.data.standardMacros. These scripts, when called from macros, provide some of the most valuable features of the Web site management facilities. The other scripts in the table might prove handy as well, and may be examined at leisure; but the following are the most important.

renderObject ( addrObject)

Processes the object at addrObject, as described earlier, under "Web Page Object Datatypes." (The normal rendering process calls this script.) The processed object is returned as a string.

renderObject() is the standard way of including one Web page object inside another. This feature substitutes for server-side includes. It also allows a Web page object of one type to be embedded in another, such as a rendered outline inside a wptext, or an outline rendered with one outline renderer script inside an outline rendered with a different outline renderer script.

An object to be included in a Web page object is often kept in a tools table, so that it will reside in the site table (for easy access) but will not be rendered as an independent HTML file when the whole site table is rendered.

imageRef ( imageObject, alt, hspace, align, usemap)

Creates an image file from the binary 'GIFf' or 'JPEG' object at imageObject, and returns an <img> tag referring to it with a relative URL. imageObject may be the address of a binary anywhere in the database, or it may be a name string, in which case it is sought in the images table in the same table as the Web page object being rendered, and then in each images table in each parent table, until the site's root-level table is reached. All but the first parameter are optional; they correspond to parameters in the <img> tag.

imageRef() is the standard way of dealing with GIFs and JPEGs to which your pages refer. You never have to write an <img> tag, because imageRef() writes it for you; and you never have to worry about placing an image file among the other files in your site, because imageRef() places it for you.17

The only hassle is that images must be stored as binaries inside the database. But it isn't much of a hassle, because the Load Image File item of the Web menu lets you choose a file and load it into the database. Such files are initially loaded into user.html.images, which isn't normally what you want; you will probably shift it to an images table in your site table. An alternative method of importing is to call html.loadImageFile() yourself, providing a file pathname and the address of a destination table.

spacePixels ( numPixels, orientation)

Creates an image file from the binary 'GIFf' object at html.data.im-ages.space, and returns an <img> tag referring to it with a width (or height) parameter of numPixels and a height (or width) parameter of 1. orientation is optional; the default is "horizontal", which causes the first option, or you can supply "vertical", which causes the second.

The idea of spacePixels() is to make an invisible spacer image. This is particularly useful in HTML tables used for formatting, to help guarantee that a cell will have a desired width.

outlineSite ( addrTable, width, indent)

Returns the HTML for an outline-like table of contents, listing and linking to all the Web pages that will be made from all the Web page objects in the site table at addrTable (to infinite depth). All parameters are optional. If addr-Table is omitted, the whole current site18 is outlined. width is the overall width of the table, in pixels; indent is the multiple of pixel indentation used for each hierarchical level of subtable.

The value of the #title directive inside each individual Web page is used as the listing for that page, from which the link to that page emanates. Each Web page object may further define a directive #subtext whose string value will be used in the table of contents as a supplementary description of that page. Further control over formatting is provided through three optional directives:

#siteOutlineHeadFont

The complete <font> tag to precede each page listing

#siteOutlineSubtextFont

The complete <font> tag to precede each #subtext description

#siteDefaultName

The name of the main (default) page for the site, to prevent it from being included in the table of contents

Unfortunately, the titles of Web page objects in each table are sorted alphabetically. It is not hard to write a utility script that makes a table of contents where you dictate the order; a utility script presented below ("Some Utility Scripts") suggests one way this might be done.

linkPrev ( linkText), linkNext ( linkText)

Returns the HTML for a relative link to the previous or next page in the site, emanating from the text linkText. (It is possible for linkText to be a call to imageRef(), to make the link emanate from a graphic.) To tell Frontier the order of pages in the site, there must be an outline called #nextPrevs in the site's root table. The #useGlossPatcher directive must be true.

To create the #nextPrevs outline, choose Build NextPrev List from the Web menu; specify the site root table in the dialog. (Capitalization counts!) A flat summit-level outline will be generated, listing all page objects at any depth in the table. Rearrange this to the desired order; for any "default" entries, delete them if they don't represent actual page objects, and correct their capitalization (if necessary) if they do.

For reasons explained later, under "Relative Links," it may be necessary to render the whole site twice in succession to make the links work correctly. In many cases this can be avoided with a utility script presented later, under "Some Utility Scripts."

embeddedUserTalk ( scriptText, linkText)

Passes scriptText through toys.URLencode() and returns the HTML for a usrtlk link emanating from linkText.

This makes a clickable link in a Web page which, if displayed in Netscape Navigator, can run a UserTalk script on the same machine as Netscape. See Chapter 34, Driving Frontier from Outside.

Glossaries

A glossary is a substitution table consisting of name-value pairs. A glossary item is an entry in such a table. During rendering, stretches of text surrounded by double quotes ("") and matching the name of a glossary item are replaced by the value of that glossary item. This provides a way for common or boilerplate text to be invoked by a convenient name in a Web page object.

Glossary items may be invoked from within page objects or templates; glossary substitution takes place after the object has been embedded in its template, as part of the same process as macro evaluation. Whether glossary substitution is performed for any particular page is determined by the value of the #expand-GlossaryItems directive.

Even if #expandGlossaryItems is true, any particular stretch of double-quoted text can be exempted from glossary substitution by preceding the first double quotation mark with a backslash; this can save considerable time during rendering. Material within angle brackets is assumed to be an HTML tag, and is protected from glossary substitution; this protection can be turned off by preceding the left angle bracket with a backslash. See "Escape Character," later in this chapter.

Glossary items are sought in a glossary as follows:

  • 1. In the table pointed to by the #glossary directive. This directive points by default to the table named glossary "closest" to the Web page object being rendered; however, it is permissible to define the #glossary directive as the address of any table.
  • 2. In any table named either glossary or #glossary, looking first in the same table as the page object being rendered, then in its parent table, and so on to root level.
  • 3. In user.html.glossary , which thus functions as a kind of global glossary table.
  • 4. If the item is not found by these means, an entirely different tack is taken: the table containing the object being rendered is examined to see if it contains a Web page object with this name; if so, a link to it is formed. This device is largely outmoded by the glossPatch mechanism (discussed later, under "Relative Links").
  • 5. Finally, if none of that works, the original stretch of double-quoted text remains untouched.

A common use of glossary items is to represent links (see user.html.glossary for many examples). Therefore, the browser's shared menu contains an item, Add to Glossary, which makes a glossary item in user.html.glossary from the URL of the page currently being browsed; the glossary item is an absolute link to that page, and it emanates from (and has the same name as) the Web page's title. A utility script provided later, under "Some Utility Scripts," transforms a glossary item whose value is a link so that it emanates from any desired text.

Escape Character

We have several times mentioned earlier ("Directives," "Macros," "Glossaries") that this or that function of the rendering engine can be prevented from operating on a particular stretch of text by the use of the backslash character. The backslash character is the escape character for the rendering engine, cancelling the normal treatment of @, //, ", {, and <.

Since the backslash is just a signal to the rendering engine, the engine removes backslashes as one of its last acts. Thus, a single backslash in a Web page object will never appear in the resulting Web page. To have a literal backslash character appear in a Web page, use two backslashes in a row in the Web page object.

However, material in angle brackets, curly braces, and double quotation marks is protected from escape-character resolution; so, for example, a single backslash within a double-quoted stretch of text will appear in the final HTML. But you can remove this protection by preceding the left angle bracket, curly brace, or double quote with a backslash!

It does no harm to escape a character that has no special meaning. The backslash will simply be removed without further effect.

Filters

The #filters directive is a table or the address of a table containing two scripts named pageFilter and finalFilter. Just before a Web page object is embedded into its template, it is placed as a string at html.data.page.bodyText , and pageFilter() is called, with no parameters; the idea is that the script may now perform whatever action is desired, possibly changing html.data. page.bodyText in some way. Similarly, just before the rendered Web page is written out to disk, it is placed as a string at html.data.page.renderedText , and finalFilter() is called, with no parameters; again, the idea is that the script may perform whatever action is desired, possibly changing html.data. page.renderedText in some way.

The default pageFilter() in a new site table does two things. First, it enlarges the first character of the page, to make a kind of drop-cap effect. In my own pageFilter(), I comment this out, but it is suggestive of the sort of thing the pageFilter() is good for. Second, it forms a glossPatch entry in the glossary table pointed to by the #glossary directive; this is important for the glossPatch mechanism of relative references within the site, as discussed later, under "Relative Links." However, as written, the whole action is in an if bundle which prevents it from operating when the object being rendered is named "default" (or "index" or whatever your default page name is); this restriction should be removed. The default finalFilter() in a new site table calls the glossPatcher() script; this should be commented out, as the same call will be made automatically elsewhere in the rendering process. By making such changes in the filter scripts within html.data.newSiteFilters, you can enforce them for every new site created with New Site.

Uses for customized filter scripts are up to your imagination. We suggest one here and another later, under "Some Utility Scripts."

Second Processing

Certain limitations of the rendering process can be overcome by adding to the finalFilter() something like this:

with html.data.page
    renderedtext = html.processMacros(renderedtext)

The routine html.processMacros() is where macro evaluation and glossary substitution are performed; it is called once during the rendering process anyway, but we are causing a second round of processing to be performed.

To see why this is useful, it is necessary to understand how html.processMacros() behaves: it passes through the text of its parameter once, looking for characters that signal special processing. If it encounters a left angle bracket, it skips to the next right angle bracket; if it encounters a double quote, it gathers up everything between it and the next double quote and tries to perform glossary substitution upon it; if it encounters a left curly brace, it gathers up everything between it and the next right curly brace and performs macro evaluation upon it. In each case, it then proceeds through the text.

Imagine, now, that your pages frequently use a little GIF that is a colored image of the word "Cool". Suppose that this image is located in an images table, where it is called (you guessed it) cool. It becomes tedious to have to invoke this image in Web page objects by saying each time:

{ imageRef ("cool") }

It would be nicer to be able to say simply:

"cool"

where this invokes a glossary item whose value is the {imageRef("cool")} macro call. But you can't do this! At rendering time, the rendering engine, having performed glossary substitution on "cool", moves on - and never sees the macro which it has just inserted. Thus a second pass through html.processMacros() is needed in order to see the macro and perform macro evaluation upon it.

Similarly, when a macro call to renderObject() is used to include one Web page object inside another, macros and glossary item invocations in the included material are not handled. A second processing pass is needed to see and handle them.

So a second round of processing sounds like a pretty good idea. But it can have repercussions that need to be guarded against. For instance, the first round of processing removes all single backslashes (see "Escape Character," earlier in this chapter). So, on the second round, characters that were protected by backslashes are no longer protected, and the actions that those backslashes were intended to prevent, and which were prevented during the first round of processing, are performed during the second round of processing. Similarly, if #autoParagraphs is true during both rounds of processing, an extra set of <p> tags will be inserted.

To prevent such repercussions, you may need to plan ahead. If you know that there will be two rounds of processing, put two backslashes where you normally would put one. On the first round of processing, the two backslashes become one backslash; on the second round, the single backslash acts as the escape character and is then removed. (A literal backslash that is to appear in the resulting Web page will have to be coded as four successive backslashes in the Web page object!)

Another way to ease the difficulty is to have the finalFilter(), just before it performs the second round of processing, turn off those directives that have already been obeyed and which should not be obeyed a second time. For example:

with html.data.page
    activeURLs = false
    autoParagraphs = false
    renderedtext = html.processmacros(renderedtext)

You also need to consider how macros will pick up the values of directives. If a directive is defined in an object included in another object with renderObject(), the definition will be handled during the first round of processing, as part of the renderObject() call. If this is a custom directive which we are picking up with a macro, the macro must be evaluated in the second round of processing rather than the first. This can be attained by postponing evaluation for one round with a backslash:

\{myDirective}

The result is that the first round of processing merely removes the backslash, and the macro {myDirective} is evaluated in the second round of processing.

Relative Links

A mechanism called glossPatch allows you to generate relative links to other pages in your site in such a way that the links will not break if you move the page objects to a different region of the site table.

There are three parts to the glossPatch mechanism:

  • 1. As each page of the site is rendered, the pageFilter() script creates a glossPatch expression for it, as an entry in the glossary table pointed to by the #glossary directive.
  • 2. In the Web page, you have used glossary item invocations wherever you want a link to a page in the site. Glossary substitution now replaces such invocations with the corresponding glossPatch expression that was generated by the pageFilter().
  • 3. Provided the directive #useGlossPatcher is true, the rendering engine calls html.data.standardMacros.glossaryPatcher() to resolve glossPatch expressions in the page object into HTML relative links.

A glossPatch expression is computer-generated, computer-readable code; it isn't intended to be useful to human beings. However, it will help our discussion of how the glossPatch mechanism works to know the format of a glossPatch expression, which is like this:

[[#glossPatch My Fourth Page|aSubTable/fourthPage|]]

This glossPatch expression would be generated by a Web page object called fourthPage whose #title directive value is "My Fourth Page". Besides the outer delimiters (two sets of square brackets), it has three parts: (a) everything up to and including the first space; (b) everything after the first space and before the first "pipe"; (c) everything between the two "pipes."

Part (a) just identifies this as a glossPatch entry.

Part (b) is the text from which the link will emanate. When the pageFilter() generates a glossary item which is a glossPatch expression, part (b) of the expression and the name of the glossary item itself are the same: they come from the page object's #title.

Part (c) is a path from the root level of the site to the page in question, with slashes showing changes of level; so, in this example, the root level of the site contains a table called aSubTable, and in that is our page object, called fourthPage.

The result is that in a Web page object, you can say, for example:

"My Fourth Page"

and this will become a link to the Web page whose title is "My Fourth Page". (The link will also emanate from the text "My Fourth Page"; this may not be what you want, so a utility script provided later, under "Some Utility Scripts," lets you change it.) What's more, you can move the object fourthPage to a different subtable of your site table, and your links to it will still work, because when the page is next rendered, the pageFilter() will change the glossPatch expression for the "My Fourth Page" glossary item.

By implication, however, for the glossPatch mechanism to work, #title values of all pages in a site must be carefully chosen! They must be unique within the entire site, and they must not be changed. If you change the #title of fourthPage, there will no longer be a glossary item "My Fourth Page" that correctly tracks the location of fourthPage; your invocations of "My Fourth Page" in Web page objects will probably break. The #title serves as a unique identifier; you're locked into a page object's #title as the price of being able to relocate the page.

It may be necessary to render the entire site twice to get relative links to work: once so that step 1 can get all the glossPatch glossary items right, and again so that steps 2 and 3 can resolve invocations of them. In many cases, this can be avoided with a utility presented later in this chapter, under "Some Utility Scripts."

Previewing and Releasing

Let's presume that you have made a Web site table with some Web page objects in it, and wish to render one or more of the objects. We now discuss how rendering is performed.

Previewing

Previewing is a mode of rendering where just one page is rendered for the purpose of viewing the resulting file in the browser. The file goes into a "temporary" folder, Websites, in the same folder as the Frontier application; the setting in #ftpSite.folder is ignored. The "temporary" folder and its contents are not deleted afterwards; it is up to you to do so from time to time.

Before previewing a page, make sure a browser is running, either Netscape or Microsoft. This is because the html suite is going to tell your browser to open the page, by way of the webBrowser suite. The webBrowser suite provides a uniform programming interface, so that the html suite can send commands to the browser without knowing which one it is; but the webBrowser suite does need to know which one it is. The agent system.agents.webBrowserAgent watches to see what browser is running, and sets suites.webBrowser.userland.currentid accordingly; this is how the webBrowser suite knows what browser to talk to.

To preview a page, select the page object, either in its edit window or in its parent table's edit window, and choose View in Browser from the Web menu. Frontier renders the page and orders the browser to display it. (With some browsers, if you previewed the page previously and the earlier version is still displayed in the browser window, you'll have to ask the browser to refresh the window in order to see the new version.)

Releasing

Releasing is a mode of rendering where one page object or an entire site table is rendered into the correct place in the folder designated by #ftpSite.folder. This might be preparatory to uploading to the remote site from which the pages are served, or in order to test features involving relative links within the site.

Before releasing, make sure a browser is running. To release a single page, select the page object in its edit window or its parent table's edit window, and choose Release Rendered Page from the Web menu. To release a table of pages, select a page object within the table's edit window (not a subtable!) or select the table itself within its parent's edit window, and choose Release Table from the Web menu.

After releasing, the browser may not be frontmost, or (worse) may not be displaying a rendered page at all. This is a very inconvenient state of affairs, but selecting a page object and choosing View Page In Finder from the Web menu can help: it takes you to the icon of the rendered HTML file in the Finder, and you can drag that icon onto the browser to view it.

If changes are made and a page or site is released again, existing files will be overwritten, but nothing will be deleted. It is up to you to clean out (or delete) the site folder manually as necessary.

Releasing Only Changed Pages

The closeWindow hook user.hooks.closeWindow.addToChangedPages() (see Chapter 24, Windows) sees to it that the address of every changed Web page object is recorded in the outline user.html.lists.changedPages . This makes it easy to release only those pages that have recently been changed; simply edit the outline and then choose Release from the Changed Pages submenu of the Web menu. This calls html.buildFromOutline() . Each object listed in user.html.lists.changedPages is released, and that line of the outline is deleted.

The same mechanism could be used to release some standard set of pages automatically. Just call html.buildFromOutline() yourself, providing as parameter the address of an outline listing the addresses of the pages. You may want the outline to be a copy, so that the entries in the original outline won't be deleted.

Releasing to a Remote Site

To release and upload to a remote site all in one motion, change #ftpSite.isLocal to false and add to #ftpSite the following entries:

domain

The URL of the remote machine as seen by your FTP client

account

Your FTP username

password

Your FTP password

directory

The path (using slashes as separators) to the directory on the remote machine where you will put the material

For example, Table 41-1 shows what my #ftpSite table looks like when I am about to release to a certain remote site (the names have been disguised to protect the innocent).

Table 41-1 An #ftpSite Table

Name

Value

account

mattn

directory

/usr/www/apache/htdocs/

domain

206.31.218.123

folder

HD:local build:

isLocal

false

password

haha

url

www.snarf.com/

An FTP client must already be running. (This is basically for the same reason that a browser must be running before rendering; the ftpClient suite is going to be used to send commands to your FTP client program, so the program must be running so that the ftpClient suite knows what program it should talk to.) When Release Rendered Page or Release Table is chosen, the material is first rendered into the local folder, then uploaded with the FTP client. However, image files created with imageRef() are not uploaded; it is up to you to upload these manually afterwards.

Loading an Existing Site

An existing Web site may be imported into the Frontier database by selecting a destination table and then choosing Load Existing Site from the Web menu. Both textfiles and image files are imported, maintaining the site structure (folders become tables).

Sites created with FrontPage, PageMill, or HomePage get some intelligent processing: the correct title is separated out, everything outside the <body> area is removed, <p> tags are cleaned out, and so on.

Object names are assigned by deriving them from file names in accordance with the dropNonAlphas , lowerCaseFileNames, maxFileNameLength and fileExtension directives. This process can assist you if you have created a Web site with a different tool and now wish to start managing it with Frontier.

Important Routines

For advanced work, such as debugging, writing utility scripts to be called from macros, and so forth, it helps to possess a mental map of the execution path traversed by the rendering engine. Additionally, some scripts in the suites.html table, though intended mostly for use by Web menu items and by other scripts, are of sufficient general utility that they might be called from a user script.

Accordingly, here is a brief sketch of some of the chief inhabitants of suites.html; this should help guide your further investigations.

html.buildObject ( addrWebPageObject)

The rendering engine. Given a Web page object, returns a string consisting of the final HTML for the fully rendered Web page.
Calls html.buildPageTable() to gather table-based directives; then html.data.standardMacros.renderObject() to gather object-based directives and to convert the page object to a string (including outline rendering). Calls the pageFilter(). Gathers directives from the template by calling html.runDirectives() or html.runOutlineDirectives(), and embeds the object into the template. Calls html.processMacros() to evaluate macros, substitute glossary items, and so forth (see on string.processHtmlMacros()) and to translate high-ASCII characters (with string. iso8859encode()). Calls the finalFilter(). Calls html.data.standardMacros.glossaryPatcher() to resolve glossPatch expressions.

You might call buildObject() from a CGI script in order to obtain HTML from a Web page object by way of the rendering engine; thus, buildObject() is the link between Frontier's CGI features and its Web site management features.19

html.buildPageTable ( addrWebPageObject, addrPageTable)

Called by html.buildObject(). Gathers table-based directives for the object at addrWebPageObject, working its way up the table hierarchy, and storing the results in the table at addrPageTable (usually html.data.page). Directives that are tables are stored as addresses.
This routine generates some useful directives that would not normally be set by the user, such as:

adrObject

Address of the object being rendered.

adrSiteRootTable

The table containing the first found occurrence of #ftpSite, indicating that this is the root level of the Web site table.

subdirectoryPath

Partial pathname for the folder that will contain the rendered file, relative to the site's root folder; thus, it can also be used for the directory of the file on the server machine, relative to #ftpsite.directory.

fname

Filename for the rendered file.

f

Full pathname for the rendered file.

url

URL for the rendered file, based on #ftpsite.url, subdirectoryPath and fname.

html.runDirectives ( string)

Called by html.data.standardMacros.renderObject() to handle string, wptext, and textfile objects; also by html.buildObject() to handle wptext templates. Gathers object-based directives; each time it finds one, deletes it and hands it to html.runDirective() for evaluation.

html.runOutlineDirectives ( addrOutline)

Called by html.data.standardMacros.renderObject() to handle outline objects; also by html.buildObject() to handle outline templates. Gathers object-based directives; each time it finds one, deletes it and hands it to html.runDirective() for evaluation, except that #define and #defineScript are specially handled.

html.processMacros ( string)

Called by html.buildObject(). Calls string.processHtmlMacros() and string.iso8869encode() in accordance with the relevant directives.

string.processHtmlMacros ( string, autoParagraphs, activeURLs, clayCompatibility, addrProc)

Called by html.processMacros(). Evaluates macros. Calls html.refGlossary() to perform glossary substitution. Obeys autoParagraphs, clay-Compatibility, and activeURLs (as directives). Simplifies escaped characters. addrProc is usually the address of the UCMD html.ucmds.embeddedcode.

The serious work of string.processHtmlMacros() is hidden in the kernel and in the UCMD, so I have had to guess what the routine actually does.

html.ftpText ( string)

Called by View In Browser and Release Rendered Page menu items. Writes string to disk as a textfile at the pathname designated by html.data. page.f. If #ftpSite.isLocal is false, also uploads the file via FTP to the server machine, using #ftpSite.directory, subdirectoryPath, and fname to work out where to put it.

html.getOneDirective ( directiveName, inString)

Searches the string inString for the first occurrence of directiveName ; evaluates and returns the rest of the paragraph in which directiveName occurs, or returns the empty string if it doesn't occur.

getOneDirective() is a brute-force method of reaching inside a Web page object to see how it defines a particular directive; it wastes no time actually handling directives, nor does it check that the rules of directive definition are obeyed - for instance, it doesn't care whether the directive name starts with # or whether it begins a paragraph or summit line. A handy utility.

html.getFileName ( nameString)

Converts nameString, typically the name of a Web page object, into a filename, in accordance with the various filename directives such as #fileExtension, #dropNonAlphas, etc.

html.getPref ( prefName)

Returns the value of the directive prefName, looking first in html.data.page, then in user.html.prefs. In most cases, returns true if the directive is undefined.

html.getSiteTable ( addrPageTable)

The parameter is optional, the default being html.data.page. Returns the address of the #ftpSite table. Needed because in theory #ftpSite might be either a table or the address of a table.

html.traversalSkip ( addrObject)

Returns true if the name of the database object at addrObject shows that it is not a renderable Web page object (i.e., it's a tools table, a directive, etc.), false otherwise, including if it is a table that might contain renderable Web page objects.

html.buildFromOutline ( addrOutline)

The outline should consist of summit-level items which are references to Web page objects. Each object is rendered to disk and its line in the outline is deleted.

html.loadImageFile ( filePath, addrDestinationTable)

Despite its name, loads any type of file at filePath as a binary (or, if a textfile, as a wptext) into the database table at addrDestinationTable, using the file's filetype as the binary's inner datatype and the file's name to construct the object's name.

Web sites can contain more than just HTML files and image files, and html.loadImageFile() could be used to get them into the database. It would be a simple matter to write a script that does for .hqx files (for instance) stored in the database what imageRef() does for image files.

Some Utility Scripts

Certain utility scripts that I have developed have come in very handy for me, and I see no reason why the neophyte Frontier Webmaster should be without their benefit. So here they are.

Munging Links

The glossSub() utility script, intended to be called from a macro, takes care of a number of limitations having to do with glossary entries and links:

  • It is common to store links as glossary items. Unfortunately, this "hardcodes" the text from which the link emanates; for example, if we have a glossary item called Frontier whose value is:
<a href="http://www.scripting.com/frontier/">Frontier</a>
  • then invoking it in a Web page object with the phrase "Frontier" will result in a link to the Frontier Web page emanating from the word "Frontier". But what if we wanted a link to the Frontier Web page, emanating from the words "a cool site"?
  • Exactly the same problem arises for glossPatch glossary items generated by the pageFilter(). It's fine that the page's #title value is used as the name of the glossary item, but we're also stuck with it as the text from which the link will emanate.
  • It would be quite easy to do HTML frames if only glossPatch glossary items could be made to include targets.

Example 41-1 contains the script.

Example 41-1 glossSub( )
on glossSub (whatGlossEntry, whatText = "&&&&", whatTarget = nil)
    local (ss = html.refGlossary(whatGlossEntry))
    on doExceptForTarget(s)
        with system.verbs.builtins.string
          if patternMatch("[[#glosspatch ", lower(s)) == 1
                html.data.page.renderedText = \
                    nthField(s,' ',1) + " " + whatText + "|" \
                        + nthField(s,'|',2) + "|]]"
                html.data.standardmacros.glossaryPatcher()
                return html.data.page.renderedText
          return nthField(s,'>',1) + ">" + whatText + "<" + nthField(s,'<',3)
    ss = doExceptForTarget(ss)
    if whatText == "&&&&"
        return string.nthField (ss, '"', 2)
    if whatTarget == nil
        return ss
    return string.replace(ss, ">", ", target=\"" + whatTarget + "\">")

glossSub() produces the HTML for a link to the same page as the link or glossPatch expression in the glossary item whatGlossEntry, but the link emanates from the text whatText (which can be a macro call to imageRef(), thus causing the link to emanate from an image). Optionally, a target parameter, whose value is whatTarget, will be included in the tag.

If both whatTarget and whatText are omitted, glossSub() returns just the URL contained in whatGlossEntry. This is useful because one might want the URL in constructing a different tag. For example, suppose three of the pages in our site are titled "First Page", "Second Page", and "Third Page"; we can then make an imageMap:

<map name="myImage">

\<area shape=rect coords = "0,0,39,33" href={glossSub("First Page")}>

\<area shape=rect coords = "39,0,106,33" href={glossSub("Second Page")}>

\<area shape=rect coords = "106,0,165,33" href={glossSub("Third Page")}>

</map>

(Notice the use of the backslash, without which a macro inside a tag would not be evaluated.) The significant thing is that the relative links will not break even if our three pages are relocated within the site, because we're getting our URLs by way of the glossPatch mechanism.

Render Once Instead of Twice

Relative links that depend upon automatically generated glossPatch glossary items typically require the entire site to be rendered twice: once to make the glossary items, and again to use them now that they are correct. The following utility prepares the glossPatch items very quickly; because it uses html.getOneDirective() to peek inside the object and get the #title directive, it works only for string, wptext, and outline Web page objects, but that's what constitutes the vast majority of sites anyway. The script can most conveniently be called from a menu item, just before rendering the site.

Example 41-2 preflightSite( )
local (whatSite = "user.websites")
if not dialog.ask ("Site table to preflight?", @whatSite) {return}
on traverse(addrT)
    local (x)
    for x = 1 to sizeOf(addrT^)
        msg(nameOf(addrT^[x]))
        if not html.traversalSkip (@addrT^[x])
            local (theType = typeOf(addrT^[x]))
            if theType != tableType
                if theType == wptextType or theType == stringType \
                or theType == outlineType
                    « adapted right from the pageFilter
                    local (stringstrip = string.lower (whatSite) + ".")
                    local (path = string.lower (@addrT^[x]) - stringstrip)
                    path = string.replaceall (path, ".", "/")
                    path = string.replaceall (path, "[\"", "")
                    path = string.replaceall (path, "\"]", "")
                    local (theTitle = html.getOneDirective("#title", \
                        string(addrT^[x])))
                    whatSite^.glossary.[theTitle] = "[[#glossPatch " \
                        + theTitle + "|" + path + "|]]"
                else « can't preflight this type, let user know
                    dialog.alert ("Could not pre-flight " + \
                        nameOf(addrT^[x]) + \
                        ", sorry. You may have to render " + \ 
                        "the whole site twice after all.")
            else « it's a table, recurse
                traverse(@addrT^[x])
traverse(whatSite)

Background Images

The #background directive lets you specify the URL of an image, but nothing is done to see that the image file is generated or in the right place. This utility remedies that. Define the #background directive with a relative URL as if the image file will be in the same folder as the page being rendered - for instance:

#background "myBg.gif"

When the page is rendered, the finalFilter() should contain a call to the following script.

Example 41-3 writeBackgroundImageFile( )
if defined(html.data.page.background)
    local
        nomad = html.data.page.adrobject
        addrImage
        imagespec = toys.popstringsuffix(html.data.page.background)
    loop « copied from imageRef()
        if nomad == html.data.page.adrSiteRootTable
            scriptError ("Can't locate an image object named \"" \
                + imagespec + "\".")
        nomad = parentOf (nomad^)
        if defined (nomad^.images)
            addrImage = @nomad^.images.[imagespec]
            if defined (addrImage^)
                break
    « write to disk, throw away HTML result
    html.data.standardmacros.imageRef(addrImage)

The script imitates imageRef(), working its way up through image tables looking for the background image. When it finds it, it calls imageRef() to write it to disk. Because we supply an address, imageRef() writes the file into the same folder as our page, which is just where the #background relative URL said it would be.

It does no harm to include in the finalFilter() a call to writeBackgroundImageFile() even if there is no background image, because in that case the routine does nothing. I have made this call part of my standard finalFilter().

Navigation Links

A common navigation aid is for each page to include a series of links to all pages in the site, or to some common subset of those pages. It is also nicer if none of these links is to the page we are actually in. Here, we illustrate one possible method of generating such links.

In order to know what other pages to link to and in what order to put the links, the script depends upon an outline which the user must already have constructed and arranged. The outline can be the NextPrev list (see "Macros," earlier in this chapter), or any summit-level list of page objects. Because html.getOneDirective() is used, the script works only when the page objects are all wptexts or outlines, but this will be the case most of the time. The script depends upon glossSub(), which was given previously.

The links do not emanate from the #title of each page, but from the value of another directive defined in each page, #subtitle. Of course you are free to modify this, and other details of the routine, to fit your own tastes.

Example 41-4 linksToOtherPages( )
on linksToOtherPages(addrO = @html.data.page.adrSiteRootTable^.["#nextPrevs"])
    local (s = "")
    on add(t)
        s = s + t
    on doOnePage(pageName)
        local
            theTitle = html.getOneDirective("title", string(pageName^))
            theSub = html.getOneDirective("subtitle", string(pageName^))
        if theTitle == html.data.page.title
            add(theSub)
        else
            add(user.html.macros.glossSub(theTitle, theSub))
        add (" | ")
    target.set(addrO)
    op.firstsummit()
    add ("\r<p>")
    doOnePage(op.getLineText())
    while op.go(down,1)
        doOnePage(op.getLineText())
    target.clear()
    return string.delete(s, sizeof(s) - 2, 3) + "</p>\r"

This algorithm could easily be adapted to modify outlineSite() so that it orders its table of contents according to the NextPrev list.

BBEdit Front-End

BBEdit can be used as an external editor for wptext Web page objects, as can PageSpinner. This is very convenient, as both these applications are extremely well suited for editing HTML. (See Chapter 35, External Editors.)

But there is an entirely different way to use BBEdit in connection with Frontier's Web site management tools: BBEdit becomes a kind of front-end to those tools, or to a simplified subset of them. Frontier and the database lurk in the background, but commands to Frontier are given through a shared menu which appears in BBEdit (the Site menu) and are handled through the bbSite suite.

Architecture

The architecture is slightly different from what I have been describing up to now. The unrendered Web site is not in the database: it is a folder of textfiles, possibly containing other folders. Templates in the unrendered Web site are files called #template.html. The directive hierarchy applies to these: to determine which #template.html goes with a file being rendered, Frontier starts in the folder that contains that file, and looks up the hierarchy until it finds the first #template.html. At the same time, #template.html files help to show Frontier where the unrendered Web site is, as a whole: to determine the root folder of the site, Frontier looks up the hierarchy from the #template.html associated with the file being rendered until it finds a folder whose parent folder does not contain a #template.html.

At the other end of the process, there is a folder into which the site will be rendered - the "output folder," designated by choosing Choose Output Folder from the Site menu.

This architecture acts as a "wrapper" to the normal Web site management features. A BBEdit file is rendered by bringing its window frontmost in BBEdit and choosing Render Page from the Site menu. Frontier thereupon reads it and its associated #template.html as wptexts into a Web site table, user.websites.bbsite, which has been created for you; there, they become a Web page object and template in a Web site table, and now the page object is just rendered in the normal way into the output folder, whose pathname has already been assigned as the value of #ftpSite.folder in user.websites.bbsite.

When you choose Render Whole Site, this same process is repeated for every file in the site, individually, starting at the folder containing the #template.html associated with the frontmost window and going down the hierarchy to infinite depth. Files whose names begin with # are not rendered. The contents of user.websites.bbsite are untouched between renderings, and subtables are created to represent folders; so that when the whole site has been rendered, user.websites.bbsite is like a Web site table for it. But the #template.html files are all read into user.websites.bbsite itself, not reflecting their place in the folder hierarchy of the site folder.

Further Details

The embedding of the object into its template is not normally performed through substitution for a <bodytext> pseudo-tag; in fact, #tagSubstitution is false. Instead, this line appears somewhere in the template:

{html.processMacros (bodytext)}

I believe that this difference from the "normal" Web page embedding mechanism is to prevent BBEdit from being confused by any <title> and <bodytext> pseudo-tags in a #template.html, when it checks HTML. However, nothing stops you from setting #tagSubstitution to true and using the normal method.

The glossary table is user.html.glossary . However, the glossPatch mechanism is operative, by default, and it places its automatically generated glossPatch glossary items into user.websites.glossary (because user.websites.bbsite has no glossary table).

Images are handled with a macro call to imageTag() , which is in user.html.macros. The syntax is:

imageTag (fileName, alt, hspace, align, usemap, height, width)

Image files in the unrendered Web site are stored in folders called images. image-Tag() looks for such a folder starting in the same folder as the #template.html associated with the file being rendered, and then up the hierarchy. It copies the file fileName from this folder into an images folder at the root level of the output folder, and returns a relative reference to it. All other parameters are optional.

Background images are handled through the finalFilter() . If the value of the background parameter in the <body> tag starts with "images/", the image file is copied out of the unrendered Web site's images folder to the correct place in the output folder.

To make it easier for users to leverage their existing AppleScript work, AppleScript script files in Script Editor format can be kept in a scripts folder. This is sought by the same method as the images folder. Its contents are loaded into user.html.macros before a page is rendered, thus making them available to be called from macros. The pathname of the file currently being rendered is available at user.bbSite.prefs.fileBeingRendered.

Limitations

This system employs the normal Web site management facilities without compelling the user to learn them; instead, the user accustomed to BBEdit and a Web site made up of files on disk is able to stay with this arrangement, while accessing as many of the normal facilities as desired. But as soon as it is desired to go beyond the simplest use of those facilities, it becomes clear that the system is a hybrid. user.websites.bbedit must be edited in order to insert table-based directives, modify the filters, add to its tools table, and so forth. Features that rely upon the whole site being present as Web page objects in a Web site table, such as outlineSite() or the NextPrev list, require rather more work to access than they normally would; in effect, the site must be rendered twice, once to assemble a Web site table and again to use it. user.websites.bbedit does not contain multiple templates in different subtables, even if the unrendered site folder contains multiple #template.html files in different subfolders. Moreover, despite the clever use of #template.html files as markers, in an important sense there is only one Web site, because there is only one output folder unless it is changed manually, and user.websites.bbedit must be cleaned out manually when necessary.

In other words, even though the really powerful parts of the Web site management facilities can, with some ingenuity, be accessed, at that point one is effectively using them in the normal manner - from a Web site table - and the file-based aspect of the BBEdit rendering method is merely in the way. A serious Webmaster managing several sites or wanting to take advantage of Frontier's most powerful site management features will therefore probably wish to switch to the normal table-based system.


1. An online hands-on tutorial (modesty prevents my mentioning the name of the author) is available at http://www.scripting.com/matt/webtutorial.

2. When you choose the New Site menu item from the Web menu, the dialog that appears expects you to type a full path to the Web site table that is to be created. Many users forget this, and type, for instance, myNewSite when they mean user.websites.myNewSite. The former does not work; it will cause a mysterious-looking error.

3. It is permitted, in theory, for the #ftpSite entry to be an address, pointing to a table; but in fact some Web site management features break if you do this.

4. It may, instead, be the string name of a wptext or outline in user.html.templates.

5. It may, instead, be the address of such a table.

6. Actually, some directives are defined, in the course of rendering, by the rendering engine; the most important of these are discussed later.

7. I have simplified by leaving out templates; see "Templates," later in this chapter. There are situations where user.html.prefs is not consulted, and there are a few cases where only user.html.prefs is consulted. There probably shouldn't be; but there are. To enumerate these would overly complicate the exposition.

8. The presence of such a <meta> tag causes some browsers to load the page twice. This is often undesirable.

9. Presumably this methodology will change as HTML style sheets become universal.

10. The directive is so named because this "hint" used to be put in the header!

11. Other possible directives for putting things in the "hint" are #menubar and #serverNetAddress, but I believe that these are no longer in general use.

12. The directive is so named because of an earlier incarnation of the Web site management facilities, called ClayBasket.

13. The cadillac renderer does not appear in the Frontier 4.2.3 clean root.

14. A number of third-party renderers are also available. My favorite is HALO, available from http://www.techsoln.com/frontier/HALO/; it looks for HTML opening tags and puts in all the corresponding closing tags, which makes it very easy to write good HTML while taking advantage of the navigational and organizational features of an outline.

15. I have coined the term "pseudo-tag" to refer to entities that look like HTML tags but are actually substitution targets for the rendering engine. Instead of <bodytext>, it is permissible to use the pseudo-tag <meat>, but this has gone out of vogue.

16. For example, the template, instead of consisting of two parts (the beginning and end of the page) with the Web page object sandwiched between them, might consist of three parts, with one half of the Web page object sandwiched between the first two, and the other half of the Web page object sandwiched between the second two.

17. If imageObject is an address, the image file ends up in the same folder with the rendered web page file; if imageObject is a name string, the image file ends up in an images folder in the site, which is created if necessary. Either way, the <img> tag points correctly to the image file.

18. As determined by #adrSiteRootTable; see later on how the rendering engine creates this directive.

19. For an example, see under "Classified Ads," in Chapter 42, Dynamic Web Sites.


TOP UP: Applied Frontier NEXT: Dynamic Web Sites

This is Frontier: The Definitive Guide by Matt Neuburg, unabridged and unaltered from the January 1998 printing.
It deals with the freeware Frontier 4.2.3; for commercial Frontier 8, look here, and for the inexpensive Radio UserLand, look here. See my Web site for information.
Those wishing an offline or printed copy may purchase the book, which is still in print.
Copyright 1998 O'Reilly & Associates, Inc. ALL RIGHTS RESERVED. Reprinted with permission.