Folks,
I've completed the first pass at multi-page input Wizards in Lift. You can
see an example at: http://demo.liftweb.net/wiz
Wizards are declarative, type-safe mechanisms for describing multi-page
workflows. They are stateful, isolated (you can run multiple copies of the
same wizard in the same browser at the same time), and testable independent
of a user interface.
Here's a 3 page wizard that chooses the flow based on the age entered on the
first screen:
/**
* An example of a wizard in Lift
*/
object MyWizard extends Wizard {
object completeInfo extends WizardVar(false)
// define the first screen
val nameAndAge = new Screen {
// it has a name field
val name = new Field with StringField {
def title = S ?? "First Name"
override def validation = minLen(2, S ?? "Name Too Short") ::
maxLen(40, S ?? "Name Too Long") :: super.validation
}
// and an age field
val age = new Field with IntField {
def title = S ?? "Age"
override def validation = minVal(5, S ?? "Too young") ::
maxVal(120, S ?? "You should be dead") :: super.validation
}
// choose the next screen based on the age
override def nextScreen = if (age.is < 18) parentName else favoritePet
}
// We ask the parent's name if the person is under 18
val parentName = new Screen {
val parentName = new Field with StringField {
def title = S ?? "Mom or Dad's name"
override def validation = minLen(2, S ?? "Name Too Short") ::
maxLen(40, S ?? "Name Too Long") :: super.validation
}
}
// we ask for the favorite pet
val favoritePet = new Screen {
val petName = new Field with StringField {
def title = S ?? "Pet's name"
override def validation = minLen(2, S ?? "Name Too Short") ::
maxLen(40, S ?? "Name Too Long") :: super.validation
}
}
// what to do on completion of the wizard
def finish() {
S.notice("Thank you for registering your pet")
completeInfo.set(true)
}
}
Each field has validation (and there's per-screen validation as well).
You can write a test for this wizard that runs independently of the UI:
object WizardSpec extends Specification {
val session: LiftSession = new LiftSession("", Helpers.randomString(20),
Empty)
"A Wizard can be defined" in {
MyWizard.nameAndAge.screenName must_== "Screen 1"
MyWizard.favoritePet.screenName must_== "Screen 3"
}
"A field must have a correct Manifest" in {
MyWizard.nameAndAge.age.manifest.erasure.getName must_==
classOf[Int].getName
}
"A wizard must transition from first screen to second screen" in {
S.initIfUninitted(session) {
MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge
MyWizard.nextScreen
MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge
MyWizard.nameAndAge.name.set("David")
MyWizard.nameAndAge.age.set(14)
MyWizard.nextScreen
MyWizard.currentScreen.open_! must_== MyWizard.parentName
MyWizard.prevScreen
MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge
MyWizard.nameAndAge.age.set(45)
MyWizard.nextScreen
MyWizard.currentScreen.open_! must_== MyWizard.favoritePet
S.clearCurrentNotices
MyWizard.favoritePet.petName.set("Elwood")
MyWizard.nextScreen
MyWizard.currentScreen must_== Empty
MyWizard.completeInfo.is must_== true
}
}
"A wizard must be able to snapshot itself" in {
val ss = S.initIfUninitted(session) {
MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge
MyWizard.nextScreen
MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge
MyWizard.nameAndAge.name.set("David")
MyWizard.nameAndAge.age.set(14)
MyWizard.nextScreen
MyWizard.currentScreen.open_! must_== MyWizard.parentName
MyWizard.createSnapshot
}
S.initIfUninitted(session) {
MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge
}
S.initIfUninitted(session) {
ss.restore()
MyWizard.prevScreen
MyWizard.currentScreen.open_! must_== MyWizard.nameAndAge
MyWizard.nameAndAge.age.set(45)
MyWizard.nextScreen
MyWizard.currentScreen.open_! must_== MyWizard.favoritePet
S.clearCurrentNotices
MyWizard.favoritePet.petName.set("Elwood")
MyWizard.nextScreen
MyWizard.currentScreen must_== Empty
MyWizard.completeInfo.is must_== true
}
}
}
The building of HTML forms from the given field type is based on the type
(currently, there's support for String and Int, but more to come) and it's
based on plugable functions, so you can build date pickers that are
localized, etc.
What needs to be done:
- The naming (including the names that the various parts of the wizard
bind to) need a good going-over
- Support for many different input types (not just Int and String)
- Feedback
So, it should be off the review board in a day or two and then I'm going to
ask for folks to try it out and give me lots of feedback.
Thanks,
David
--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics
--
You received this message because you are subscribed to the Google Groups
"Lift" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/liftweb?hl=en.