2A 27 2C 197 4ACD Sam's All New, New and Improved, New News news news
Falvotech.
Conquering the world is easy — what do you do with it afterwards?

L I N K S


⇑ Home

⇐ Unsuitable on Unsuitable: Index Generation

⇒ Memo: I'm Sick and Fumigation Down-Time


Unsuitable on Unsuitable: Rendering Articles
Samuel A. Falvo II
kc5tja -at- arrl.net
2010 Aug 07 13:26 PDT

Viewing articles on most blogs implies receiving a plurality of advertisements and other distractions as well. This reduces both the reader's attention span for the material presented, and valuable screen real-estate. Unsuitable renders an article with a minimum of distraction. With more room on the screen for content, I can provide a nicer layout that makes reading easier for the consumer.

1 Introduction

Take a careful look at the screenshot1 below, and see if you can find all of the things wrong with it.

Which of the following things do you consider sins against all mankind?

  1. Browser toolbars plus the blog banner consumes greater than 50% vertical screen space. It's bad enough that browser toolbars consume a fair amount of screen real-estate, but documents browsed should never try to actively compound the problem.
  2. No less than seven user solicitations competing for the user's attention on this part of the page alone. (See if you can find them all.)
  3. Considering the 2-dimensional nature of the video display, less than 11% of the screen contains relevant material. Yes, you read that right: Technorati dedicates greater than 89% of the screen to useless drivel. The index page link to this article contains a larger subset of the article's text than this initial view does.
  4. Compulsory scrolling needed to even begin reading the article, with continued wasted screen real-estate below. Even after scrolling, your screen remains less than 50% utilized; the remainder contains yet more reader solicitations.
  5. Bad HTML coding. (They obviously don't test their site with Firefox.)
  6. Nightmarish color scheme grants the banner vastly superior visual priority over the content I wanted to actually read.

I concede some lack of fairness on my part; readers should not take Technorati as a representative of all blogs. However, I argue the majority of blogs attempting to derive ad-based revenue, particularly as their sole source of income, will possess one or more of the aforelisted sins against their customers. While you can configure Unsuitable for this kind of Alice in Wonderland presentation with some effort, for Unsuitable lets you configure the visual appearance via its theme directory and various HTML-generating macros, I deliberately chose to keep its out-of-the-box presentation utterly simple and to the point. I abhore online distractions.

Below, I detail both the page rendering logic, which should remain (mostly) unchanged regardless of the page layout, and the individual macros used to provide the default theme.

2 Code Walk-Through

Regrettably, the walk-through which follows will not follow the definitions in source listing order. I observed in a previous article, yet failed to highlight at that time, the importance of definition order in successfully defining macros so that they will not interfere with HTML response generation at run-time2. For both index and RSS generation modules, so few macros existed that I considered the order of definition a non-issue. With m-articles.fs, however, large numbers of macro-related definitions separates the two halves of the page rendering logic. Readers may find this distracting, so I've decided to elide or re-arrange chunks of code to help illustrate how the pieces fit together.

2.1 Page Rendering Logic

m-articles.fs accepts an article identifier parameter on the URL path. If the user insists on omitting this parameter, we present the user with the blog's index page.

end-of-url ¶meters - constant /parameters
: oops              s" m-index.fs" included bye ;
: |parameters|>=2   /parameters 2 u< if oops then ;
|parameters|>=2

We calculate the address of the first byte of the parameter in the URL string.

¶meters 1+ constant &id

We expect an integer parameter; anything else drops us back to the index page of the blog.

: -eon     dup end-of-url u>= if drop scan valid then ;
: -0-9     dup [char] 0 < swap [char] 9 > or ;
: *10+     dup c@ [char] 0 - id @ 10 * + id ! ;
: digit    dup c@ -0-9 if drop oops then *10+ ;
: numeric  &id begin -eon digit char+ again ;
numeric

Once we know we have a numeric parameter, we need to perform a full table scan to locate the corresponding article information needed to render the page. The variable id already contains the desired article number.

: n!          articleId n @ <   n @ -1 =  or if articleId n ! then ;
: nxt         articleId id @ > if n! then ;
: prv         articleId id @ <  articleId p @ >  and if articleId p ! then ;
: exists      nxt prv ;
: row         dup articleIds - article! articleId -1 xor if exists then ;
: -all        [ articleIds /afields + ] literal u< ;
: scan        articleIds begin dup -all while row cell+ repeat drop ;

I apologize if the above code confuses you; it was written before I fully realized the DItI pattern. I'll attempt to summarize what's happening.

scan performs a full-table scan, looking not only for the current article ID, but also its immediate predecessor and successor as well. See On Determining an Article's Next and Previous Links: A Quick Thought for a mathematical definition of predecessor and successor in the context of Unsuitable.

When scan completes, three variables, later used with macro expansions, will refer to relevant articles. Note: none of the variables listed below refer to row offsets in the articles table. All variables hold identifiers. For example, if you use article ID 1035 in the URL, id will equal 1035, p 1034, and n 1036.

variable id  0 id ! This variable stores the identifier of the article to display.
variable p   -1 p ! This variable holds the previous article's ID. If none exists, it retains its default value of -1.
variable n   -1 n ! This variable holds the next article's ID. If none exists, it retains its default value of -1.

Once we know as much as we need to know about the article requested, we declare our state valid, which invokes HTML response generation.

variable s
variable end
: mime   ." Content-type: text/html" cr cr ;
: valid   mime  here s ! s" theme/article.html" slurp here end ! s" response.fs" included bye ;

2.2 Macros

Unsuitable renders individual articles with a relatively large set of macros. Macro selection and desired page layout influence each other; therefore, many macros listed below exist primarily to facilitate the desired page layout. If the layout were to change in the future, I will replace legacy macros with macros relevant to the new layout.

Macro Generates
Output?
Description
~pred N Causes ~label to evaluate to the label of the current article's predecessor.
~succ N Causes ~label to evaluate to the label of the current article's successor.
~span Y Generates appropriate HTML <span> tags. Unsuitable renders the <span> tag differently, depending on whether a predecessor/successor article exists at all.
~label Y If it exists, this macro evaluates to the predecessor or successor article title. Otherwise, this macro expands to the text No article written yet.
~/span Y This macro closes the HTML <span> tag emitted by the ~span macro.
~title Y This macro reproduces the current article's title.
~timestamp Y This macro reproduces the current article's publication timestamp.
~lead Y This macro reproduces the current article's abstract or lead.
~body Y/N This macro reproduces the current article's body, if it has one. Otherwise, no output occurs.

To see how these macros work in context, see section 4, the listing for theme/article.html.

The ~pred and ~succ macros affect whether other rendering macros operate on the current article's predecessor or successor. which maintains this state by pointing either p or n, depending on whether ~pred or ~succ last executed, respectively.

variable which

: succ     n which ! ;
: pred     p which ! ;

The ~span, ~/span, and ~label macros all depend on the state of which, and whether or not the appropriate article exists. Unsuitable employs a two-dimensional jump matrix which maps the macros to the desired behaviors.

create +/-
  ' +span ,   ' +/span ,   ' +label ,   0 ,
  ' -span ,   ' -/span ,   ' -label ,   0 ,

: go       which @ @ -1 = 4 cells and + @ execute ;
: span     [ +/- ] literal go ;
: /span    [ +/- cell+ ] literal go ;
: label    [ +/- 2 cells + ] literal go ;

At the time I wrote this code, I originally used four macros to render the LINKS column. I later altered it to use only three macros. Out of laziness, I never bothered to reduce the size of the decision matrix.

Each macro may vector to an article-exists variant (+x) or to an article-absent variant (-x).

: -span    s\" <span style=\"color: #ddd;\">" respond ;
: -/span   s" </span>" respond ;
: -label   s" No article written yet." respond ;

: url      safely s" http://www.falvotech.com/blog2/blog.fs/articles/" respond  articleId s>d <# #s #> respond ;
: +span    s\" <a href=\"" respond url s\" \">" respond ;
: +/span   s" </a>" respond ;
: +label   safely title gob! get, ;

I introduce a word safely, extending Forth's control flow words such that I may execute arbitrary Forth code in the context of either the successor or predecessor article, without visibly affecting the current article selection from the user's standpoint. safely preserves relevant state for us, enabling us to continue to work with the user's article selection later on.

: invoke   >r ;
: safely   r>  article >r  which @ @ articleWithId!  invoke  r> article! ;

The ~timestamp macro prints the article's publication time.

: timestamp   >web id @ articleWithId! timestamp .time >con ;

The remaining three macros wrap the getters found in the articles.fs module by augmenting each of them with the ability to dump their contents to the HTML response.

: title       ['] title .f ;
: lead        ['] lead .f ;
: body        ['] body .f ;

If a field comes up missing, however, we want to ensure no output occurs.

: exists:     dup -1 = if r> 2drop then ;
: .f          id @ articleWithId! execute exists: gob! get, ;

3 What's Next

Well, this article concludes the detailed walk-through of Unsuitable's program code. I hope you have enjoyed reading about how Unsuitable works as much as I've enjoyed writing these articles. Some readers have e-mailed me with some suggestions to make the blog more usable for them. As I complete work on the feature requests submitted, I'll post articles detailing the changes I make to Unsuitable to accomodate each feature.

I have other Forth projects in the works which I'll talk about here in the future, as well. And, if you're good boys and girls, I just might have another Over The Shoulder video for you too. Stay tuned.

4 Source Listing for theme/article.html

<html>
 <head>
  <title>
   Sam's All New, New and Improved, New News news news
  </title>
  <link rel="stylesheet" href="/blog2/theme/css.css" />
 </head>
 <body>
  <div class="blogHead">
   Falvotech.
  </div>
  <div class="blogSubhead">
   Conquering the world is easy &mdash; what do you do with it afterwards?
  </div>
  <hr />
  <table width="100%">
   <tr>
    <td width="15%" valign="top" align="left">
     <div>
      <p align="center">L I N K S</p>
      <hr />
      <p>~pred ~span &lArr; ~label ~/span </p>
      <p>~succ ~span &rArr; ~label ~/span </p>
      <hr />
     </div>
    </td>
    <td width="85%" valign="top" align="left">
     <div class="blogArticleTitle">
      ~title
     </div>
     <div class="blogArticleTimestampAuthor">
      <div class="blogArticleAuthor">Samuel A. Falvo II<br />kc5tja -at- arrl.net</div>
      <div class="blogArticleTimestamp">~timestamp </div>
     </div>
     <div class="blogArticleLead">
      ~lead
     </div>
     <div class="blogArticleBody">
      ~body
     </div>
    </td>
   </tr>
  </table>
 </body>
</html>

5 Source Listing for m-articles.fs

require mappings.fs
require general.fs
require articles.fs
require slurp.fs
require time.fs
require respond.fs

end-of-url ¶meters - constant /parameters
: oops              s" m-index.fs" included bye ;
: |parameters|>=2   /parameters 2 u< if oops then ;
|parameters|>=2


variable id  0 id !
variable p   -1 p !
variable n   -1 n !
variable which

: -span    s\" <span style=\"color: #ddd;\">" respond ;
: -/span   s" </span>" respond ;
: -label   s" No article written yet." respond ;

: invoke   >r ;
: safely   r>  article >r  which @ @ articleWithId!  invoke  r> article! ;

: url      safely s" http://www.falvotech.com/blog2/blog.fs/articles/" respond  articleId s>d <# #s #> respond ;
: +span    s\" <a href=\"" respond url s\" \">" respond ;
: +/span   s" </a>" respond ;
: +label   safely title gob! get, ;

create +/-
  ' +span ,   ' +/span ,   ' +label ,   0 ,
  ' -span ,   ' -/span ,   ' -label ,   0 ,

: go       which @ @ -1 = 4 cells and + @ execute ;
: span     [ +/- ] literal go ;
: /span    [ +/- cell+ ] literal go ;
: label    [ +/- 2 cells + ] literal go ;
: succ     n which ! ;
: pred     p which ! ;


variable s
variable end
: mime   ." Content-type: text/html" cr cr ;
: valid   mime  here s ! s" theme/article.html" slurp here end ! s" response.fs" included bye ;


: exists:     dup -1 = if r> 2drop then ;
: .f          id @ articleWithId! execute exists: gob! get, ;
: title       ['] title .f ;
: lead        ['] lead .f ;
: body        ['] body .f ;
: timestamp   >web id @ articleWithId! timestamp .time >con ;


: n!          articleId n @ <   n @ -1 =  or if articleId n ! then ;
: nxt         articleId id @ > if n! then ;
: prv         articleId id @ <  articleId p @ >  and if articleId p ! then ;
: exists      nxt prv ;
: row         dup articleIds - article! articleId -1 xor if exists then ;
: -all        [ articleIds /afields + ] literal u< ;
: scan        articleIds begin dup -all while row cell+ repeat drop ;


¶meters 1+ constant &id
: -eon     dup end-of-url u>= if drop scan valid then ;
: -0-9     dup [char] 0 < swap [char] 9 > or ;
: *10+     dup c@ [char] 0 - id @ 10 * + id ! ;
: digit    dup c@ -0-9 if drop oops then *10+ ;
: numeric  &id begin -eon digit char+ again ;
numeric

1  Screenshot captured on 2010 Aug 7, on my home computer running GoboLinux 014rc2 and Firefox 3.6.

2  Defining macros in another module and including them midway through the m-articles.fs source listing would ameliorate this problem greatly. Of course, one only thinks of these kinds of things when retrospecting on one's work.