Skip to content
ripla edited this page Aug 22, 2012 · 23 revisions

This tutorial is designed to show how to get a web-application running with Vaadin, MongoDB and Scala. The tutorial was inspired by the Scala, Play 2 and Mongo example @ yobriefca.se. The whole example project (containing both minimal and full implementations of the application) can be found at https://github.com/ripla/vaadin-scala-mongo

Prequisites

All of the above can be installed via separate downloads or with a package manager. For this tutorial the following software & library versions were used

  • Scala 2.9.2
  • SBT 0.11.3-2
  • Giter8 0.4.5
  • MongoDB 2.0.7
  • Casbah 2.4.1
  • Salat 1.9.1
  • Scaladin 2.0
  • Vaadin 6.8.2

This tutorial also assumes that you have MongoDB running in the background. If not, the MongoDB website has some excellent quickstart guides.

Generating app structure

Start off by generating your application skeleton with the Giter8 template.

g8 ripla/vaadin-scala
package [com.example]: org.vaadin.scala.example.mongo
name [Vaadin Scala project]: Mongo Example
classname [VaadinScala]: MongoExample

Applied ripla/vaadin-scala.g8 in mongo-example

This will create a working application skeleton in the mongo-example sub-dir. The application already includes Vaadin and Scaladin. Try it out!

cd mongo-example
sbt

And in the SBT console:

container:start

The application should now be started and respond at http://localhost:8080.

In the following sections we'll be editing the files in the project. If you'd like to use Eclipse, you can type

eclipse

in the SBT console to create Eclipse .project file in the root. Then just import the project to Eclipse.

Adding dependencies

Next we're going to add the required dependencies for to help out with Mongo. Edit the build.sbt file at the project root and add the Sonatype snapshots repo by modifying the resolvers.

resolvers ++= Seq("Vaadin add-ons repository" at "http://maven.vaadin.com/vaadin-addons",
			 "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots")

Then, add the Casbah and Salat dependencies by modifying the library dependencies.

libraryDependencies ++= Seq(
    "org.mongodb" %% "casbah" % "2.4.1",		
    "com.novus" %% "salat" % "1.9.1-SNAPSHOT"
)

Reload the project, either by restarting SBT or typing reload in the console.

Writing the application

The purpose of this software is to provide a simple mechanism for registering users, and viewing those registrations in a list. The app is so simple that it can be implemented in a single application file - and we're going to do just that (in about 100 lines or so).

DB connection and data mapping

First we'll create our data class as a simple Scala case class. We'll use the @BeanProperty annotation so that our data class acts like a good citizen in the Java world (e.g. it has getters&setters for fields). We also set some default values for the fields.

case class Registration(
    @BeanProperty var username: String = "username" + Random.nextInt,
    @BeanProperty var password: String = "",
    @BeanProperty var realName: String = "Joe Tester")

One of the great things about MongoDB is that we don't need to create stores or collections in the database manually. Simply assuming that one exists creates one for you. For the database connection, we're going to leverage Casbah.

  val registrations: MongoCollection = MongoConnection()("vaadin-scala-mongo-example")("registrations")

This creates a connection to a Mongo store called vaadin-scala-mongo-example and a collection registrations. Next, we'll define a method for mapping the Casbah DBObject objects to actual registration objects with Salat and vice-versa.

def mapRegistrations: List[Registration] = registrations.map(grater[Registration].asObject(_)).toList
def saveRegistration(registration: Registration): Unit = registrations += grater[Registration].asDBObject(registration)

And that's about it. The application should look something like this https://gist.github.com/3363699.

###UI components In Scaladin, the main layout is usually composed inside the main method which returns a ComponentContainer. Just start filling the UI presented in the next sections like so:

override val main: ComponentContainer = new VerticalLayout {
    ... // UI components here
}

Time to start. Let's create a layout containing a Table and a Button.

val tableLayout = new VerticalLayout {
  val table = new Table {
    sizeFull()
    container = new BeanItemContainer(mapRegistrations)
    visibleColumns = Seq("username", "realName")
  }

  val addButton = Button("Register", showForm)
  components += (table, addButton)
}

def showForm(): Unit = { /*TODO*/ }

If you're accustomed to Vaadin development, the snippet should be somewhat self-explanatory. We create a VerticalLayout, a Table and a Button and add the Table and the Button to the VerticalLayout.

The Table is full size, and its data container is a BeanItemContainer that takes all the mapped Registration objects and shows them. BeanItemContainer uses Java reflection mechanisms under the hood, so this is why we needed the BeanProperty annotations in the Registration object. By default Table shows all the properties from the Container so we need to set set the visible columns and their order. showForm is a function that is called when the Button is clicked. We'll fill it out later.

Next, we'll create the Form for adding new registrations.

lazy val form = new Form {
  caption = "Registration"
  writeThrough = false
  footer = new HorizontalLayout {
    components += Button("Save", showList)
  }
}

def showList(): Unit = { /*TODO*/ }

Again, nothing out of the ordinary. Just a lazily instantiated Form with a caption and a footer with a button that calls a function. writeThrough = false simply means that the data entered to the the form isn't automatically set to the underlying registration object, but instead needs to be explicitly committed.

If you add the Table to the main layout:

components += tableLayout

you should see the (empty) registration list and the Button in the UI. The application should look someting like this: https://gist.github.com/3372122 (there's some extra fluff to make the UI look prettier)

###Application logic

Time to fill out those methods to get the application to actually do something. Let's start with showForm.

def showForm(): Unit = {
  form.item = new BeanItem(Registration())
  form.visibleItemProperties = Seq("realName", "username", "password")
  replaceComponent(tableLayout, form)
}

This function was previously bound to the Button so it gets called whenever the Button is clicked. In the function we set the backing Item of the Form and we use the ever-so-handy BeanItem and an empty registration object for this. Again, because we used the BeanProperty annotation BeanItem finds our fields automatically. And like with the Table, we need to set the order of these fields. Last, we replace the Table with the Form in the main layout.

def showList(): Unit = {
  if (form.commit.isValid) { //form handles error
    val bean = form.item.get.asInstanceOf[BeanItem[Registration]].bean
    saveRegistration(bean)
    tableLayout.table.container = new BeanItemContainer(mapRegistrations)
    tableLayout.table.visibleColumns = Seq("username", "realName")
    replaceComponent(form, tableLayout)
  }
}

After filling the form, we try to commit the values to the underlying bean. If the validation succeeds we can proceed. Otherwise, we let the Form show the errors. After a successful commit the bean is saved and the list of registrations is updated to the Table. The application should look like this https://gist.github.com/3372871.

###Fine-tuning the fields

The last thing to is to set the field properties. By default, Form uses DefaultFieldFactory to create the fields. They're sensible defaults, but we can modify them a bit. To be more exact, we want to make the fields required, change the password to be a PasswordField and add a password confirmation field. The easiest way is to write our own FormFieldFactory:

val registrationFormFieldFactory = FormFieldFactory(ing => {
  var field: Option[Field] = ing match {
    case FormFieldIngredients(_, "password", _) =>
      Some(new PasswordField {
        caption = DefaultFieldFactory.createCaptionByPropertyId("password")
      })

    case FormFieldIngredients(_, "confirmation", form: Form) =>
      Some(new PasswordField {
        caption = "Confirm password"
        validators += Validator(value => {
          if (value == form.field("password").get.value) Valid
          else Invalid(List("Passwords must match"))
        })
      })

    case otherIngredient => {
      DefaultFieldFactory.createField(otherIngredient)
    }
  }

  field.foreach(_.required = true)
  field.foreach(f => f.requiredError = "%s is required".format(f.caption.get))
  field
})

In essence FormFieldFactory is a function of FormFieldIngredients(propertyId,item,uiContext) => Option[Field]. And that's exactly what the match-case expression in our FormFieldFactory does. It has cases for fields named password or confirmation, and uses the DefaultFieldFactory for everything else. In addition it sets every field as required with a nice error message. The foreach is required because field is an Option[Field] - DefaultFieldFactory might not create a field at all and we have to prepared for that.

Lets look the password confirmation field a bit more.

validators += Validator(value => {
  if (value == form.field("password").get.value) Valid
  else Invalid(List("Passwords must match"))
})

Validators are another function, this time value: Any -> Valid | Invalid(reason). In our confirmation field validation we use the Form instance that's included in the FormIngredients given to us and read the password value from there. (Actually, FormIngredients contains an instance of Component but in this case we know it must be a Form.)

Since the confirmation field isn't included in the actual registration bean (it wouldn't make much sense there) it has to be explicitly added to the Form. This can be done in the showForm method:

form.addField(Option("confirmation"), form.formFieldFactory.flatMap(_.createField(FormFieldIngredients(form.item.get, "confirmation", form))))

Last but not least, we need to actually set the Form to use the FormFieldFactory we've defined. Add this line to the Form creation function:

formFieldFactory = registrationFormFieldFactory

And that's it, we're all set. The final class can be found here. However, this is a single file solution that's more of an tool for this tutorial than a good implementation example. A better example (e.g. one with more than one class) can be found in the full package.