The Big Scheme of Things: Episode 2 -- Templates

October 10, 2009

While attending GPCE, SLE, and MoDELS in Denver, I recorded episode 2 of The Big Scheme of Things screencast series. Using a simple to-do application as example application, I illustrate various aspects of the use of WebDSL, a domain-specific language for web applications.

In this episode, I show the use of templates to divide a page definition into smaller fragments, the definition of action in templates, and the use of icons as actions.

</embed>
Download: 512x384, 768x576, 1024x768

[This is as much an experiment in screencasting, as it is a WebDSL tutorial. Feedback is welcome. Thanks for the feedback on the previous episode. For this recording I have used a proper microphone (Rode Podcaster) instead of the built-in microphone of my MacBook; sound quality should be better. I still have to look into other formats than Quicktime; suggestions for useful formats are welcome. An iTunes feed is in the planning as well. There was a request for a start and end tune; apart from the requirement that such a tune should be copyright free, music is not my strong point; suggestions are welcome.]

Better Data Model for Tasks

First the entity declaration for Tasks is extended to hold relevant data:

  entity Task {
    name        :: String
    description :: WikiText
    done        :: Bool
    logged      :: Bool
    isDue       :: Bool
    dueDate     :: Date
    created     :: DateTime
    finished    :: DateTime
  }

In this episode we focus on the done and logged status, which indicate that a task is completed and that it is to be moved to the log, respectively.

Refactoring to Templates

Next the root page definition is factored into templates:

  define page root(){
    addTask()
    tasks()
    loggedComplete()
    logged()
  }

For example, the following template captures the list of open tasks:

  define tasks() {
    section{
      header{"Tasks"}
      list{
        for(t : Task where !t.logged order by t.created desc) {
          listitem{ task(t) }
        }
      }
    }
  }

The task template defines the interface for a single Task, showing its status and

Note that the template is parameterized with a task object and is instantiated for each task that is not logged in the iteration above.

  define task(t : Task) {
    action check() {
      t.done := !t.done;
      if(t.done) {
        t.finished := now();
      } else {
        t.logged := false;
      }
      refresh();
    }
    if(t.done) {
      image("/images/accept.png")[onclick:=check()]
    } else {
      image("/images/process.png")[onclick:=check()]
    }
    output(t.name) " "
    if(t.done) { output(t.finished) }
  }

Code

Below is the full code as realized at the end of the episode. For this to work you also need the icons. I used the freely available Luna Grey icon set.

application bigscheme

section data model

  entity Task {
    name        :: String
    description :: WikiText
    done        :: Bool
    logged      :: Bool
    isDue       :: Bool
    dueDate     :: Date
    created     :: DateTime
    finished    :: DateTime
  }

section user interface

  define page root(){
    addTask()
    tasks()
    loggedComplete()
    logged()
  }
  
  define tasks() {
    section{
      header{"Tasks"}
      list{
        for(t : Task where !t.logged order by t.created desc) {
          listitem{ task(t) }
        }
      }
    }
  }

  define logged() {
    section{
      header{"Logged"}
      list{
        for(t : Task where t.logged order by t.finished desc limit 3) {
          listitem{ task(t) }
        }
      }
    }
  }

  define addTask() {
    var task := Task{}
    form{
      input(task.name)
      action("+", action{ task.save(); })
    }
  }

  define task(t : Task) {
    action check() {
      t.done := !t.done;
      if(t.done) {
        t.finished := now();
      } else {
        t.logged := false;
      }
      refresh();
    }
    if(t.done) {
      image("/images/accept.png")[onclick:=check()]
    } else {
      image("/images/process.png")[onclick:=check()]
    }
    output(t.name) " "
    if(t.done) { output(t.finished) }
  }

  define loggedComplete() {
    action moveToLog() {
      for(t : Task where t.done && !t.logged) {
        t.logged := true;
      }
    }
    action("Logged Complete", moveToLog())
  }