PillowUser Guide
Overview
Pillow
is a web framework that maps HTTP request URIs to Pillow Pages, letting them react to RESTful events.
Pillow
...
Pillow
- Something for your web app to get its teeth into!
Install
Install Pillow
with the Fantom Repository Manager ( fanr ):
C:\> fanr install -r http://repo.status302.com/fanr/ afPillow
To use in a Fantom project, add a dependency to build.fan
:
depends = ["sys 1.0", ..., "afPillow 1.0+"]
Documentation
Full API & fandocs are available on the Status302 repository.
Quick Start
1). Create a text file called Example.efan
:
Hello Mum! I'm <%= age %> years old!
2). Create a text file called 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=TemplateDirectories# } static Void contributeEfanDirs(Configuration config) { // Look for Example.efan in the same dir as this fantom file config.add(`./`) } }
3). Run Example.fan
as a Fantom script from the command line:
C:\> fan Example.fan Efan Library: 'app' has 1 page(s): Example : /example
4). 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 URL.
Welcome Pages
Pillow
supports the routing of welcome pages, also known as directory pages, through the WelcomePageStrategy.
When switched on, whenever a request is made for a directory URI (one that ends with a /slash/) then Pillow
will render the directory's welcome page, which defaults to a page named 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?
The welcome page strategy also supports redirects, where requests for legacy pages (like /index.html
) are redirected to the directory URI. Redirects are preferred over serving up the same page for multiple URIs to avoid duplicate content.
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 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 { @PageContext abstract Str name @PageEvent Void loves(Str meat) { echo("${name} loves ${meat}!") } }
Event URIs follow the pattern:
<page name> / <page context(s)> / <event name> / <event context(s)>
So we can call the loves
event with the URI http://localhost:8069/example/Emma/loves/sausage
, which is broken down as:
example --> 'Example#' page type Emma --> 'name' field loves --> 'loves()' method sausage --> 'meat' argument
Use PageMeta.eventUri(name, context)
to generate event URIs that can be used in templates.
Event methods are invoked before anything is rendered. The default action, should the event method be Void
or return null
, is to re-render the containing page.
Event methods may return any BedSheet response object.
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 contexts by using the withContext()
method. Using our example again:
@Page const mixin Example : EfanComponent { @Inject abstract PageMeta pageMeta @PageContext abstract Int age Str renderLinkToSelf() { return "<a href='${pageMeta.pageUri}'>Link to ${age}</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 route handlers. The Pillow PageMeta
handler will then render the Pillow page.
Use the Pages service to create PageMeta
instances for any Pillow page.
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
v1.0.16
- Bug: Bodged release.
v1.0.14
- New: Added
@PageEvent.name
which overrides the default method name. - New: Added
PageMeta.eventMethods()
. - Chg: Updated to IoC 1.7.2 and BedSheet 1.3.12.
- Chg: Renamed
PageMeta.eventUri
->PageMeta.eventUrl
.
v1.0.12
- New: Added a default
cache-control
HTTP header as a config value. - Chg: Renamed
PageMeta.pageUri
->PageMeta.pageUrl
.
v1.0.10
- New: Stack frames from
Pillow
,efanXtra
andefan
are marked as boring on BedSheet's Err500 page. - Chg: Updated to IoC 1.6.4.
v1.0.8
- New: Using Bean Utils 0.0.2
- New: Page state from events are saved and restored should the page be rendered as part of the same request.
- Chg: Page events render the containing page by default.
- Chg: Renamed
@Page.uri
->@Page.url
v1.0.6
- Chg: Updated to use efanXtra 1.1.0.
- Chg:
Pages.pageMeta()
andPages.get()
now throw aNotFoundErr
if the given page type could not be found.
v1.0.4
- Chg: Page context may be nullable on
Pages.pageMeta()
. - Chg: Added
Pages.get()
operator for easyPageMeta
access. - Chg: Contributed
Pillow Pages
section to BedSheet's Err and Not Found pages.
v1.0.2
- Chg: Page context values may now be immutable / non-const objects.
v1.0.0
- New: Implemented
WelcomePageStrategy
. - New: Added helpful http response headers for testing.
- Chg:
PageUriResolver
andContentTypeResolver
are now configurable. - Chg: Renamed ConfigId
welcomePage
->welcomePageName
. - Bug: Page URIs with no page context could be a directory URI.
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. (DeletedRenderPageMeta
.) - New: Added
PageMetaResponseProcessor
that renders Pillow pages whenPageMeta
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
(fromafBedSheetEfanExtra
). - Chg: Reanmed
EfanPageMeta
toRenderPageMeta
. - Chg: Renamed
PageRoute
toPageUri
.
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