1 Installing Left Paren
2 Tutorials
2.1 Hello, World
2.2 Blogerton the Blog
2.2.1 Changes to app.scm
2.2.2 Changes to main.scm
2.2.3 Launch Blogerton
3 Reference
3.1 Forms
3.1.1 Image uploads
3.2 Sessions
3.2.1 Creating sessions
3.2.2 Accessing sessions
3.3 Users
3.4 Feeds
4 About/ Acknowledgements
4.1 Code Contributors
Version: 4.1.3.9

LeftParen 0.5 Documentation

Website: http://leftparen.com

LeftParen is a framework for quickly creating web apps. It runs on PLT Scheme v4.1.3.9 or greater. (This is not-yet-officially-released, so you’ll have to download it from the PLT Pre-Relese Software page.) LeftParen is released under an MIT License. The source is available on github.

1 Installing LeftParen

You’ll need PLT Scheme v4.1.3.9 or greater installed.

Make sure that mzscheme is in your path. You should be ready to go if you can do this:

% mzscheme
Welcome to MzScheme v4.1.3.9...
>

Installing LeftParen is done automatically when you perform various PLaneT require commands. See Tutorials for examples of this. On my system, starting a LeftParen web app for the first time (with a freshly installed version of PLT Scheme), takes about an hour. (The time is spent downloading and installing a bunch of files into your local PLaneT repository.)

At any point, if you see an error like

make-directory: cannot make directory: /home/rob/.plt-scheme/planet/300/4.1.3.9 (Permission denied; errno=13)

try re-running the command with sudo (you won’t need to do this everytime–just the once).

2 Tutorials

2.1 Hello, World

We’re going to make a project called hello-world. Change to the directory that you’d like to create the project in. Then issue

% mzscheme -e '(require (planet vegashacker/leftparen:4:0/generate))' hello-world

This will create a hello-world project directory for you. In this directory you’ll find the script directory, which contains some useful scripts. All paths are relative to this project directory, so when calling scripts, you always want to be at the project root.

% cd hello-world

We need to make the scripts executable:

% chmod u+x script/server

LeftParen has automatically generated everything we need to run our web app – we just need to start the server (again, you should be at the project root directory):

% ./script/server
Populating caches...
Done populating cache.
Server is ready at http://localhost:8765/ (ctrl-c to stop it).

Point your browser to http://localhost:8765 and you should see a familiar greeting:

Hello, World!

2.2 Blogerton the Blog

Now let’s try implementing the true "hello world" of web apps – a blog. First, execute the following commands from the directory in which you want to create your project:

% mzscheme -e '(require (planet vegashacker/leftparen:4:0/generate))' blogerton
% cd blogerton
% chmod u+x script/server

2.2.1 Changes to app.scm

We need to register a couple of pages in our app. The index-page was already set up for you, but you’ll need to add a page to create new posts, and one to view them. Make the define-app call look like this:

  (define-app my-app
    (index-page (url "/"))
    (create-post-page (url "/post"))
    (view-post-page (url "/view/" (string-arg))))

2.2.2 Changes to main.scm

Now we need to define those pages that we declared in app.scm.

  (define-page (index-page req)
    (** `(h1 "Blogerton")
        `(p ,(web-link "Create a new post" (page-url create-post-page)))
        `(ul ,@(map (lambda (p) `(li ,(paint-blog-post p)))
                    (load-where '((type . blog-post))
                                #:sort-by 'created-at #:compare >)))))
  
  (define-page (create-post-page req)
    (form '((title "Title" text) (body "Body" long-text))
          #:init '((type . blog-post))
          #:on-done (lambda (post) (redirect-to-page view-post-page (rec-id post)))))
  
  (define-page (view-post-page req post-id)
    (paint-blog-post (load-rec post-id #:ensure '((type . blog-post)))))
  
  (define (paint-blog-post post)
    `(div (h2 ,(rec-prop post 'title))
          (p ,(rec-prop post 'body))))

2.2.3 Launch Blogerton

You’re ready for launch. Start the server with

% ./script/server

and you should have a basic blogging app, with persistent data, in 19 lines of code.

3 Reference

3.1 Forms

Most web applications make some use of web forms. The form function lets easily you get and process input from your users.

(form field-specs)  xexpr?
  field-specs : (listof field-spec?)

field-spec? : (list symbol? string? field-type?)

field-type? : 
(or/c 'text 'long-text 'number 'password 'image 'checkbox
      'radio 'drop-down)

You create a form by listing, in order, "field specifications". For example, you might want a title field, followed by a description text box, followed by a photo upload field. Note that the many keyword arguments available to the form function aren’t documented yet.

Each field spec is of the form (field-name label field-type). For example, you create a title field, you might use the spec (title "Enter a title" text). The entire example metioned above might look like this:

  (form '((title "Title" text)
          (description "Description" long-text)
          (photo "Your photo" image)))

3.1.1 Image uploads

By default, uploaded images are stored in the uploaded-files directory in your project directory. You can customize this with the *PATH_TO_UPLOADED_FILES* setting. When images are saved, their original filenames are used with a 5-character code pre-pended to make filenames unique.

3.2 Sessions

A session is an object that allows you to easily store state about individual visitors to your web app. Sessions are stored on the server as a record with a virtually impossible-to-guess id. A cookie is left in the user’s web browser, which contains a pointer to a particular session id. These cookies expire one month after creation and, currently, this can’t be changed.

3.2.1 Creating sessions

(define-session-page
  (page-name request-iden session-iden page-args ...)
  body ...)

This is an alternate to define-page, most commonly used in main.scm. The only difference is that after the request identifier, you must provide a session identifier. For example, to keep a counter (unique to each user), you could write:

  (define-session-page (foo-page req sesh)
    (let ((c (session-get-val sesh 'counter 0)))
      (session-put-val! sesh 'counter (+ 1 c))
      (number->string c)))

When you define a session page, the session is automatically fetched for you (and created if necessary), and bound to the session identifier you provided.

3.2.2 Accessing sessions

(session-get-val session key [missing-val])  any
  session : session
  key : symbol
  missing-val : any = #f

(session-put-val! session key val)  session
  session : session
  key : symbol
  val : any

3.3 Users

LeftParen provides built-in functionality for dealing with users, including registering users, logging users in and out, and storing persistent data about users. To get up-and-running quickly, you can use the high-level welcome-message function:

(welcome-message session    
  [#:on-success success-fn    
  #:no-register no-register])  xexpr
  session : session?
  success-fn : (or/c (-> user? xexpr?) #f) = #f
  no-register : boolean? = #f

The function welcome-message produces a small area of text and links (commonly found in the top-right area of a web app). If the user is not currently logged in, login and register links are presented. If the user is logged in, a message welcoming them is displayed, along with a link to log out.

(current-user session)  (or/c user? #f)
  session : session?

The function current-user returns the current user record, or #f if no user is available in the current session.

As an example, here is the complete page code for a web app that allows users to register, login and logout, and which prints a secret message if the user is logged in:

  (define-session-page (index-page req sesh)
    (** (welcome-message sesh)
        (aif (current-user sesh)
             (format "The secret, ~A, is 42." (rec-prop it 'username))
             "No secret for you.")))

3.4 Feeds

You can create Atom or RSS feeds in your web app. A feed in LeftParen is just a page crafted in a paricular way. The core functions involved are atom-feed and rss-feed:

(atom-feed atom-feed-page 
  #:feed-title feed-title 
  #:feed-updated/epoch-seconds updated-seconds 
  #:author-name author-name 
  [#:feed-description feed-description 
  #:feed-id feed-id 
  #:related-content-link related-content-link 
  #:items atom-items]) 
  response/full?
  atom-feed-page : page?
  feed-title : string?
  updated-seconds : integer?
  author-name : string?
  feed-description : (or/c #f string?) = #f
  feed-id : string? = THE_URL_OF_THE_GIVEN_ATOM_FEED_PAGE
  related-content-link : string? = THE_LINK_TO_YOUR_WEB_APP
  atom-items : (list-of atom-item?) = '()

(rss-feed rss-feed-page 
  #:feed-title feed-title 
  #:feed-description feed-description 
  [#:related-content-link related-content-link 
  #:items rss-items]) 
  response/full?
  rss-feed-page : page?
  feed-title : string?
  feed-description : string?
  related-content-link : string? = THE_LINK_TO_YOUR_WEB_APP
  rss-items : (list-of rss-item?) = '()

The #:items argument in each of these functions is a list of items constructed with atom-item and rss-item:

(atom-item #:title title 
  #:url url 
  #:updated-epoch-seconds updated-seconds 
  [#:content content]) 
  atom-item?
  title : string?
  url : string?
  updated-seconds : integer?
  content : (or/c #f string?) = #f

(rss-item #:title title    
  #:url url    
  [#:content content])  rss-item?
  title : string?
  url : string?
  content : (or/c #f string?) = #f

Here’s an example Atom feed page:

  (define-page (article-feed-page req)
    #:blank #t
    (atom-feed article-feed-page
               #:feed-title "LeftParen blog"
               #:feed-description "On LeftParen..."
               #:feed-updated/epoch-seconds (current-seconds)
               #:author-name "LP staffers"
               #:items (list
                        (atom-item #:title "Status update..."
                                   #:url "http://blog.../50308696"
                                   #:updated-epoch-seconds
                                   (current-seconds)
                                   #:content "I’m nearing a...")
                        (atom-item #:title "LeftParen 0.3..."
                                   #:updated-epoch-seconds
                                   (current-seconds)
                                   #:url "http://blog.../51814971"
                                   #:content "Tonight I..."))))

Note that while using current-seconds for timestamps does satisfy the interface, it’s not really appropriate since these times are supposed to indicated freshness of the data. If basing your feed off of records, you might consider using created-when.

4 About/Acknowledgements

LeftParen was initially written by Rob Hunter. It makes use of Dispatch by Untyped, with-bindings by Jay McCarthy, and of course, PLT Scheme.

LeftParen used to use WEB by Jens Axel Soegaard and Instaservlet (now deprecated) by Untyped, which were both very helpful in getting the project started.

4.1 Code Contributors