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 Contributors
Version: 4.1.0.3

LeftParen 0.4 Documentation

Website: http://leftparen.com

LeftParen is a framework for quickly creating web apps. It runs on PLT Scheme v4.1 or greater. LeftParen is released under an MIT License. The source is available on github.

1 Installing LeftParen

You’ll need PLT Scheme v4.1 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...

>

Installing LeftParen is done behind the scenes with a PLaneT require. See Tutorials for an example of this. When you first issue one of these require commands, you’ll automatically download the LeftParen files to your local PLaneT cache. This can sometimes take a few moments, so be prepared to wait a bit.

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 make the project in. Then issue

% mzscheme -e '(require (planet "bootstrap.scm" ("vegashacker" "leftparen.plt" 3 0)))' project 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/*

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

Web server started on port 8765

Listening on IP address: 127.0.0.1

Type stop to stop the server and exit

Type restart to restart the server

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 directory:

% mzscheme -e '(require (planet "bootstrap.scm" ("vegashacker" "leftparen.plt" 3 0)))' project blogerton

% cd blogerton

% chmod u+x script/*

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 written by Rob Hunter, but it builds heavily on (and, in fact, often directly incorporates) the work of Untyped (instaservlet and dispatch), Jens Axel Soegaard (web.plt), and of course, PLT Scheme.

4.1 Contributors