#2195 [ANN] afEfanExtra v0.0.2 - Reusable Efan Components

SlimerDude Tue 15 Oct 2013

afEfanExtra v0.0.2 (Preview Release)

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
  }
}

Then to render a component:

efanExtra.render(Overdue#, "Mr Smith")

Full example source code available on BitBucket.

Components

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:

/fan/components/Layout.fan
/fan/components/Layout.efan

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:

app.renderOverdue(Str userName)
app.renderSignOff(Str who)

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)
    }
  }
}

Have fun!

:)

Login or Signup to reply.