afEfanExtra is a library for creating managed libraries of reusable Embedded Fantom (efan) components. Influenced by Java's Tapestry 5, it pairs up Fantom classes and efan templates to encapsulate model / view behaviour.
afEfanExtra is very much in its infancy, with ideas still being fleshed out. It works great in a web / afBedSheet environment, with URLs being automatically mapped to components (coming soon...!), but is presented here context free.
afEfanExtra extends afEfan, is powered by afIoc and works great with afSlim.
Quick Start
Overdue.efan:
Dear <%= userName %>,
It appears the following rented DVDs are overdue:
<%= dvds.join(", ") %>
Please return them at your convenience.
<% app.renderSignOff("The Management") %>
Overdue.fan:
using afIoc
using afEfanExtra
@Component
const mixin Overdue {
// use afIoc services!
@Inject abstract DvdService? dvdService
// template can access mixin fields
abstract Str? userName
// called before the component is rendered
Void initialise(Str userName) {
this.userName = userName
}
// methods may be called from the template
Str[] dvds() {
dvdService.findByName(userName)
}
}
AppModule.fan:
using afIoc
@SubModule { modules=[EfanExtraModule#]}
class AppModule {
static Void bind(ServiceBinder binder) {
binder.bindImpl(DvdService#)
}
@Contribute { serviceType=EfanLibraries# }
static Void contributeEfanLibraries(MappedConfig config) {
// contribute all components in our pod as a library named 'app'
config["app"] = AppModule#.pod
}
}
An efan component consists of a Fantom const mixin class and a corresponding efan template file.
Mixins
Component mixins must be annotated with the @Component facet.
All fields and methods of the mixin are directly accessible in the template. You can even use afIoc's @Inject facet to inject services just as you would in a service class.
Use an initialise() method to pass state into an efan component (the ctx variable is not used). Only one initialise() is allowed. It must be named initialise but may take any number of parameters.
Templates
By default, the template file has the same name as the component (with a .efan extension) and lives anywhere in a pod resource dir. Example component files:
Ensure /fan/components/ is listed in your build.fan as a resDir.
ALIEN-AID: Note resource directories in your build.fan are NOT nested. Adding res/ will NOT add /res/components/ - /res/components/ would need to be added seperately. Example:
resDirs = [`doc/`, `res/`, `res/components/`]
Libraries
Components are managed in libraries. To package up your components, add to the following to your app module:
@Contribute { serviceType=EfanLibraries# }
static Void contributeEfanLibraries(MappedConfig config) {
// contribute all components in the pod as a library named 'app'
config["app"] = AppModule#.pod
}
Library classes are automatically added as fields in your components. Library classes contain component render methods. In the example above, the library (in a field named app) has 2 render methods, available to your templates:
ALIEN-AID: Library render methods are logged at registry startup so you don't have to remember them!
Use with Slim
afEfanExtra works great with afSlim! Add the following to your AppModule and afEfanExtra will automatically pick up component templates with the extenstion .slim:
using afIoc
using afSlim
using afEfanExtra
class AppModule {
static Void bind(ServiceBinder binder) {
binder.bindImpl(Slim#)
}
@Contribute { serviceType=TemplateConverters# }
internal static Void contributeSlimTemplates(MappedConfig config, Slim slim) {
config["slim"] = |File file -> Str| {
slim.parseFromFile(file)
}
}
}
SlimerDude Tue 15 Oct 2013
afEfanExtra v0.0.2 (Preview Release)
afEfanExtrais a library for creating managed libraries of reusable Embedded Fantom (efan) components. Influenced by Java's Tapestry 5, it pairs up Fantom classes and efan templates to encapsulate model / view behaviour.fanr install -r http://repo.status302.com/fanr/ afEfanExtraafEfanExtrais very much in its infancy, with ideas still being fleshed out. It works great in a web / afBedSheet environment, with URLs being automatically mapped to components (coming soon...!), but is presented here context free.afEfanExtraextends afEfan, is powered by afIoc and works great with afSlim.Quick Start
Overdue.efan:
Dear <%= userName %>, It appears the following rented DVDs are overdue: <%= dvds.join(", ") %> Please return them at your convenience. <% app.renderSignOff("The Management") %>Overdue.fan:
using afIoc using afEfanExtra @Component const mixin Overdue { // use afIoc services! @Inject abstract DvdService? dvdService // template can access mixin fields abstract Str? userName // called before the component is rendered Void initialise(Str userName) { this.userName = userName } // methods may be called from the template Str[] dvds() { dvdService.findByName(userName) } }AppModule.fan:
using afIoc @SubModule { modules=[EfanExtraModule#]} class AppModule { static Void bind(ServiceBinder binder) { binder.bindImpl(DvdService#) } @Contribute { serviceType=EfanLibraries# } static Void contributeEfanLibraries(MappedConfig config) { // contribute all components in our pod as a library named 'app' config["app"] = AppModule#.pod } }Then to render a component:
Full example source code available on BitBucket.
Components
An efan component consists of a Fantom
const mixinclass and a corresponding efan template file.Mixins
Component mixins must be annotated with the
@Componentfacet.All fields and methods of the mixin are directly accessible in the template. You can even use afIoc's
@Injectfacet to inject services just as you would in a service class.Use an
initialise()method to pass state into an efan component (thectxvariable is not used). Only oneinitialise()is allowed. It must be namedinitialisebut may take any number of parameters.Templates
By default, the template file has the same name as the component (with a
.efanextension) and lives anywhere in a pod resource dir. Example component files:Ensure
/fan/components/is listed in yourbuild.fanas a resDir.Libraries
Components are managed in libraries. To package up your components, add to the following to your app module:
@Contribute { serviceType=EfanLibraries# } static Void contributeEfanLibraries(MappedConfig config) { // contribute all components in the pod as a library named 'app' config["app"] = AppModule#.pod }Library classes are automatically added as fields in your components. Library classes contain component render methods. In the example above, the library (in a field named
app) has 2 render methods, available to your templates:Use with Slim
afEfanExtraworks great with afSlim! Add the following to yourAppModuleandafEfanExtrawill automatically pick up component templates with the extenstion.slim:using afIoc using afSlim using afEfanExtra class AppModule { static Void bind(ServiceBinder binder) { binder.bindImpl(Slim#) } @Contribute { serviceType=TemplateConverters# } internal static Void contributeSlimTemplates(MappedConfig config, Slim slim) { config["slim"] = |File file -> Str| { slim.parseFromFile(file) } } }Have fun!
:)