Show in Frame No Frame
Up Previous Next Title Page Index Contents Search

5.2.8 Additional exercise

As a final task of our exercise we will add some interactivity into our HTML documents by linking each Watch’s page to the spec sheets of previous and next Watches. There is again a little catch here: the first page only has a link to the previous page and the last page only has a link to the previous page, while intermediate pages can link both ways. This means that there is going to be some variation when outputting the HTML for individual Watch pages.

There is of course more than one way to implement this requirement. We will explore two different ways to do this, starting with the one that is probably the most obvious to most people with any previous programming experience. In this solution we create an index for each Watch model first and then use this index when we need to fetch the information about the previous and next models while creating the spec sheet for a Watch model. The relevant parts of this solution look like this:
@ix='0'
foreach >Watch; orderby y num
{ local 'ix_' @++ix write id close }

@ix='0'
foreach >Watch; orderby y num
{ .
  .
  '<p class="navigation">'
  @prev = __(local 'ix_' @ix++ read)
  @next = __(local 'ix_' @++ix-- read)
  if @prev then
    'Previous model: ' _linkHTML(@prev)
  endif
  if @prev AND @next then ' | ' endif
  if @next then
    'Next model: ' _linkHTML(@next)
  endif
  '</p>' newline
  .
  .
}
After executing the first three lines we will have the MERL equivalent of an array, a set of local variables that store the name for the Watch model at the respective index, i.e.:
@ix_1 = Ace
@ix_2 = Delicia
@ix_3 = Sporty
@ix_4 = Celestron
@ix_5 = Celestra
After this initialization, we reset @ix and enter the main foreach loop where we assign the @prev and @next local variables with the names of the Watch models preceding and following the one currently at hand. As the syntax here may initially appear a bit cryptic, we will walk through it step by step. Consider the first assignment:
@prev = __(local 'ix_' @ix++ read)
Starting from within the local...read command in the parentheses, we first construct the name of the index variable pointing to the previous Watch model:
'ix_' @ix++
The name of the variable starts always with prefix ‘ix_’ that is followed by the index number. The counter arithmetic in this case reads as: output the current value of @ix first, and then increment @ix by 1. Hence, we will get the name for the index variable (i.e. ‘ix_0’, ‘ix_1’ etc.) and have our counter updated. After this step, the assignment line is thus effectively:
@prev = __(local 'ix_0' read)
The local...read command will fetch the value for the named local variable. At this point it is worth pointing out that there is no ix_0 among our existing index variables. Reading the value of an as-yet-undefined variable just returns an empty string, which is what we want here as there is no predecessor for our first Watch model. The surrounding __(...) is just used so that we can have multiple or non-trivial commands on the right-hand side of the assignment: it’s actually a call to a subgenerator called ‘__’, which simply returns the value of the argument built up between the parentheses. The subsequent iterations will result in the values of variables @ix_1, @ix_2 etc., which will give us the name of the preceding Watch to store in @prev. We can then use that later to output the appropriate HTML for a link to the file of the same name, in a subgenerator _linkHTML(@prev) whose definition is left to the reader.

The assignment of the @next variable is otherwise similar to above, but the name for the index variable is constructed differently:
'ix_' @++ix--
The name starts with the ‘ix_’ prefix again but is followed now by the index number of the next Watch model. The counter arithmetic thus now reads: increment counter by 1 (to get the next index), output its value, and then decrease it by one (to get back to current index as we need it during the next step of iteration). Otherwise the assignment executes as with @prev. On the final Watch model we will encounter the variable name ‘ix_6’ that is not pointing anywhere and will end up having @next as blank – which is again what we want as there is no Watch model following the last one.

The solution above uses MERL’s associative variables like an Array, i.e. it forms key and value pairs where the key is a numerical index and the value is a Watch model name. However, this requires us to keep track of index counters during the creation and use of the array. It is also possible to use associative variables in a more organic way, more like a Dictionary or HashMap than an Array, by using Watch names as keys instead of building numerical indices to use as a key. This way we no longer need to maintain the information about indices. The new variable names also make the code more readable and understandable. Rather than trying to write “C in MERL”, we are using MERL in a more natural way. Using this approach, the complete SpecSheet() generator will now look like this:
SpecSheet()
_translators()
@defaultDir = __(subreport '_default directory' run)
@cssFilename = _myFilename('css')
filename @defaultDir @cssFilename write _css() close
@prev = ''
foreach >Watch; orderby y num
{	local 'prev_' id write @prev close
	local 'next_' @prev write id close
	@prev = id
}
foreach >Watch; orderby y num
{ filename @defaultDir _myFilename('html') write
    '<html>' newline
    '<head><link rel="stylesheet" type="text/css" '
      'href="' @cssFilename '"/></head>' newline
    '<body>' newline
    '<h1>' id '</h1>' newline
    '<p class="doc">' :Documentation '</p>' newline
    do .Display
    { @count = '0'
      do :UnitZones { @count++%null }
      '<p class="info">Number of time units shown: '
      @count '</p>' newline
      '<p class="info">Buttons: '
      dowhile :Buttons { id ' ' }
      '</p>' newline
    }
    do .LogicalWatch
    { '<p class="info">Apps: '
      do decompositions
      { foreach .Start [Watch]
        { $visited = ' '
          do ~From~To.()
          { _handleStateAndRecurse() }
        }
      }
      '</p>' newline
    }
    '<p class="navigation">'
    @prev = __(local 'prev_' id read)
    @next = __(local 'next_' id read)
    if @prev then
      'Previous model: ' _linkHTML(@prev)
    endif
    if @prev AND @next then ' | ' endif
    if @next then
      'Next model: ' _linkHTML(@next)
    endif
    '</p>' newline
    '</body>' newline
    '</html>' newline
  close
}
The execution of the first foreach loop will create the following variables:
@prev_Ace =
@next_Ace = Delicia
@prev_Delicia = Ace
@next_Delicia = Sporty
@prev_Sporty = Delicia
@next_Sporty = Celestron
@prev_Celestron = Sporty
@next_Celestron = Celestra
@prev_Celestra = Celestron
@next_Celestra =
Now, by knowing the id (i.e. the name) of the current Watch model we can construct the names for variables that point to its processor and successor. Within the second loop we will now use this trick to create the @prev and @next local variables as we did earlier. Otherwise the generator remains the same as before.

The final output of this generator will now result in a web page like this:

HTML page generated from Watch Model

Figure 5–9. Interactive web page generated from the Watch Example.

The generator could still be improved, e.g. to make sure that any characters illegal as literals in HTML, like & and <, are escaped when found in the model. This can be handled simply by appending %xml to the relevant output commands, e.g. id%xml and :Documentation%xml.

Adding these final touches to our exercise completes this primer for developing generators with MERL in MetaEdit+. After this general overview of generator development practices, the following chapters will provide more comprehensive descriptions of the MERL development tools like the Generator Editor and Debugger, and of the individual MERL commands.

Show in Frame No Frame
Up Previous Next Title Page Index Contents Search