PillowUser Guide

Overview

Pillow is a web framework that maps URIs to Pillow Pages, letting them react to RESTful events.

Pillow...

Pillow - Something for your web app to get its teeth into!

Install

Install afPillow with the Fantom Respository Manager:

C:\> fanr install -r http://repo.status302.com/fanr/ afPillow

To use in a Fantom project, add a dependency to its build.fan:

depends = ["sys 1.0", ..., "afPillow 0+"]

Quick Start

Example.efan:

Hello Mum! I'm <%= age %> years old!

Example.fan:

using afIoc
using afBedSheet
using afEfanXtra
using afPillow

// ---- The only class you need! ----

@Page
const mixin Example : EfanComponent {

    @PageContext
    abstract Int age
}

// ---- Standard Main Class ----

class Main {
    Int main() {
        afBedSheet::Main().main([AppModule#.qname, "8069"])
    }
}

// ---- Support class, needed when running from a script ----

@SubModule { modules=[EfanXtraModule#, PillowModule#] }
class AppModule {
    @Contribute { serviceType=EfanTemplateDirectories# }
    static Void contributeEfanDirs(OrderedConfig config) {
        // Look for Example.efan in the same dir as this file
        config.add(`./`)
    }
}

Run the Example.fan script from the command line:

C:\> fan Example.fan

Efan Library: 'app' has 1 page(s):
  Example : /example

Then point your browser at http://localhost:8069/example/42:

Hello Mum! I'm 42 years old!

Usage

To create a web page, define an EfanComponent that is annotated with the Page facet. Example:

using afPillow::Page
using afEfanXtra::EfanComponent

@Page
const mixin Admin : EfanComponent {
  ...
}

Pages are efanXtra components and behave in exactly the same way.

Pillow will automatically route URLs with your page name, to your page. Camel casing class names results in a / delimiter. Examples:

`/admin`        --> Admin.fan
`/admin/secret` --> AdminSecret.fan

Or you can use the @Page facet to define an explicit URI.

Welcome Pages

Pillow supports the routing of welcome pages, also known as directory pages.

If Pillow recieves a directory uri (a uri that ends in /slash/) then it renders a welcome page, which defaults to Index. Examples:

`/`        --> Index.fan
`/admin/`  --> AdminIndex.fan

More can be read about directory URLs in the article: Should Your URLs Point to the Directory or the Index Page?

Page Contexts

As seen in the Quick Start example, parts of the request path are automatically mapped to @PageContext fields. In our exmaple, the 42 in http://localhost:8069/example/42 is mapped to the age page context field.

Declaring page context fields is actually shorthard for assigning fields manually from the @InitRender method. The Quick Start example could be re-written long hand as:

@Page
const mixin Example : EfanComponent {
    abstract Int age

    @InitRender
    Void initRender(Int age) {
        this.age = age
    }
}

Note that a Pillow Page may choose to have either an @InitRender method or @PageContext fields, not both. Also note that page context objects need to be immutable (const classes).

Page Meta

The PageMeta class holds information about the Pillow Page currently being rendered. Obviously, using PageMeta in a page class, returns information about itself! Which is quite handy.

Arguably the most useful method is pageUri() which returns a URI that can be used, by a client, to render the page complete with the current page context. You can create new PageMeta instances with different page context by using the withContext() method. Using our example again:

@Page
const mixin Example : EfanComponent {
    @Inject PageMeta pageMeta

    @PageContext
    abstract Int age

    Str renderLinkToSelf() {
        return "<a href='${pageMeta.pageUri}'>Link to 42</a>"
    }

    Str renderLinkTo69() {
        page69 := pageMeta.withContext([69]).pageUri
        return "<a href='${page69}'>Link to 69</a>"
    }
}

PageMeta instances are BedSheet response objects and may be returned from request handlers. The handler then renders the Pillow page. Use the pageMeta() method in the Pages service to create PageMeta objects for arbituary pages.

Page Events

Page events allow pages to respond to RESTful actions by mapping URIs to page event methods. Page event methods are called in the context of the page they are defined. Denote page events with the @PageEvent facet.

Lets change our example so that the page context is a Str and introduce an event called loves:

@Page
const mixin Example : EfanComponent {
    @Inject PageMeta pageMeta

    @PageContext
    abstract Str name

    @PageEvent
    Obj loves(Str obj) {
        echo("${name} loves ${obj}!")
        return pageMeta
    }
}

We can call the event using the URI http://localhost:8069/example/Emma/loves/sausage.

/example/Emma would render the page, but should a link to /example/Emma/loves/sausage be clicked, then the page event loves is called.

Page events may return any standard BedSheet response object. Above we return the pageMeta to re-render the page with a page context of Emma.

Use the PageMeta.eventUri(name, context) to create full event URIs that can be used by clients.

Content Type

Page template files should use a double extension in their name, for example,

IndexPage.xhtml.slim

The outer extension denotes the type of templating to use, Slim in our example. The innter extension is used to find the Content-Type that is sent in the HTTP response header. In our example, the Content-Type would be set to application/xhtml+xml.

If a double extension is not used, or not know, then the default content type, as defined by the config value, is used.

Or you can use the @Page facet to explicitly set the content type.

Release Notes

v0.0.10

  • New: Added @PageEvent methods allowing URIs to be mapped to page methods.
  • New: Added @PageContext fields that can replace @InitRender methods.
  • New: Added the PageMeta class to wrap up, um, page meta data! Oh, and attached an instance to the rendering pages. (Deleted RenderPageMeta.)
  • New: Added PageMetaResponseProcessor that renders Pillow pages when PageMeta instances are returned as BedSheet response objects.
  • New: Added httpMethod field to @Page and @PageEvent
  • New: Added template uri to @Page
  • Chg: Page is now a facet, incorporating fields from @PageUri and @PageContentType (which have now been deleted).

v0.0.8

  • New: Use the @PageContentType facet to explicitly define the content type for your page.
  • New: Use a double extension (e.g. indexPage.xhtml.slim) to set the content type for the page.
  • Bug: @InitRender params could incorrectly match for directory index pages.

v0.0.6

  • New: Page uris and BedSheet routes are generated from the @InitRender method signature.
  • New: Directory uris may now serve welcome pages.
  • Chg: Updated to use BedSheet 1.2.
  • Chg: Renamed project to afPillow (from afBedSheetEfanExtra).
  • Chg: Reanmed EfanPageMeta to RenderPageMeta.
  • Chg: Renamed PageRoute to PageUri.

v0.0.4

  • New: Added @PageRoute facet that lets you specify a bespoke uri
  • New: Added EfanPageMeta which returns the active rendering page.

v0.0.2

  • New: Preview Release