Created
June 19, 2012 14:14
-
-
Save mariusdanciu/2954444 to your computer and use it in GitHub Desktop.
Scala formlet example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package formlets | |
import scala.xml._ | |
object Main extends App { | |
case class Person (name: String, age: Int) | |
case class Subject(person: Person, userName: String) | |
val person = (Person(_, _)).curried | |
val subject = (Subject(_, _)).curried | |
import Formlet._ | |
def validName(name: String) : Map[String, String] => Validation[List[String], String] = env => env.get(name) match { | |
case Some(n) => Success(n); | |
case _ => Failure(List("Missing name value")) | |
} | |
def validAge : Map[String, String] => Validation[List[String], Int] = env => env.get("age") match { | |
case Some(age) => try { | |
val intAge = age.toInt | |
if (intAge >= 18) | |
Success(intAge) | |
else | |
Failure(List("Age must be higher than 18")) | |
} catch { | |
case e: Exception => Failure(List(age + " is not a number")) | |
} | |
case _ => Failure(List("Missing name value")) | |
} | |
val form = Formlet(person) <*> | |
inputText("name")(validName("name")).label("id", "User name: ") <*> | |
inputInt("age")(validAge).attr("id", "ageId") | |
println(form html) | |
val p = form validate Map(("name" -> "marius"), ("age" -> "33")) | |
println(p) | |
// Now let's compose forms | |
val subjectForm = Formlet(subject) <*> form <*> inputText("address")(validName("userName")) | |
println(subjectForm html) | |
println( subjectForm validate Map(("name" -> "marius"), ("userName" -> "mda"), ("age" -> "33"))) | |
} | |
sealed trait Validation[+E, +A] | |
case class Failure[E](e: E) extends Validation[E, Nothing] | |
case class Success[A](a: A) extends Validation[Nothing, A] | |
class ReversedApplicative[A, Env, Err](form: Form[A, Env, Err]) { | |
def <*>[X, Y](f: Form[X, Env, Err])(implicit e: A <:< (X => Y), s: Semigroup[Err]): Form[Y, Env, Err] = new Form[Y, Env, Err] { | |
override val validate : Env => Validation[Err, Y] = env => { | |
(form.validate(env), f.validate(env)) match { | |
case (Failure(e1), Failure(e2)) => Failure(s append (e1, e2)) | |
case (_, Failure(e)) => Failure(e) | |
case (Failure(e), _) => Failure(e) | |
case (Success(a1), Success(a2)) => Success( a1( a2 ) ) | |
} | |
} | |
override def html = form.html ++ f.html | |
} | |
} | |
trait Form[A, Env, Err] {me => | |
val validate : Env => Validation[Err, A] | |
def html: NodeSeq = NodeSeq.Empty | |
def label(id: String, text: String): Form[A, Env, Err] = new Form[A, Env, Err] { | |
val validate = me validate | |
override def html: NodeSeq = <label for={id}>{text}</label> ++ me.html | |
} | |
def attr(name: String, value: String): Form[A, Env, Err] = new Form[A, Env, Err] { | |
val validate = me validate | |
override def html: NodeSeq = me.html match { | |
case elem : Elem => elem % new UnprefixedAttribute(name, value, Null) | |
case e => e | |
} | |
} | |
} | |
trait Semigroup[A] { | |
def append(a: A, b: A): A | |
} | |
object Formlet { | |
implicit def formToApp[A, Env, Err](form: Form[A, Env, Err]): ReversedApplicative[A, Env, Err] = new ReversedApplicative(form) | |
implicit def listSemigroup[A]: Semigroup[List[A]] = new Semigroup[List[A]] { | |
def append(a: List[A], b: List[A]): List[A] = a ::: b | |
} | |
def apply [A, Env, Err](a: => A): Form[A, Env, Err] = new Form[A, Env, Err] { | |
val validate : Env => Validation[Err, A] = env => Success(a) | |
override def html = NodeSeq.Empty | |
} | |
def inputText[Env, Err](name: String)(f: Env => Validation[Err, String]) = new Form[String, Env, Err] { | |
val validate = f | |
override def html = <input type="text" name={name} /> | |
} | |
def inputInt[Env, Err](name: String)(f: Env => Validation[Err, Int]) = new Form[Int, Env, Err] { | |
val validate = f | |
override def html = <input type="text" name={name} /> | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment