Skip to content

Instantly share code, notes, and snippets.

@SRGOM
Created February 6, 2015 22:00
Show Gist options
  • Save SRGOM/9a711b56d37d0ae60dc5 to your computer and use it in GitHub Desktop.
Save SRGOM/9a711b56d37d0ae60dc5 to your computer and use it in GitHub Desktop.
package scalatags
import scala.language.implicitConversions
import scala.collection.JavaConverters._
import scala.collection.immutable
import scalatags._
import scalatags.generic.{AttrPair, Util}
import scalatags.Text.{TypedTag, attrs}
import scalatags.Text.all._
/**
* Form field attributes. In line with what spray/twirl (also known as playframework/twirl)
* uses right now
*/
trait FormFieldAttrs[Builder, Output<: FragT, FragT] extends Util[Builder, Output, FragT] {
val `class` = "class".attr
val cls = `class`
val default = "default".attr
val error = "error".attr
val help = "help".attr
val id = "id".attr
val label = "label".attr
val showConstraints = "showConstraints".attr
val showErrors = "showErrors".attr
val text = "text".attr
val All = Set( cls, default, error, help, id, label, showConstraints, showErrors, text )
}
object FormHelper{
trait FieldConstructor{
def apply(elements: FieldElements): TypedTag[String]
}
object FieldConstructor{
implicit val defaultFieldConstructor = new FieldConstructor{
def apply(elements: FieldElements): TypedTag[String]
= {
val errorClass = elements.hasErrors match {
case true => "error"
case _ => ""
}
val argClass = elements.args.find( _.a == fieldAttrs.`class` ).map( _.v.toString ).getOrElse( "" )
val id = elements.args.find( _.a == fieldAttrs.id ).map( _.v.toString ).getOrElse( elements.id + "_field" )
dl( attrs.cls := s"$argClass $errorClass", attrs.id := id )(
dt(
label( attrs.`for` := elements.id )(
elements.label(elements.lang)
)
),
dd( elements.input ),
elements.errors( elements.lang ).map { error => dd( attrs.cls := "error" )( error ) },
elements.infos( elements.lang ).map { info => dd( attrs.cls := "info" )( info ) }
)
}
}
}
object fieldAttrs extends Text.Cap with FormFieldAttrs[ text.Builder, String, String ]
/**
* Generate an HTML input checkbox.
*
* Example:
* {{{
* checkbox(field = myForm("done"))
* }}}
*
* @param field The form field.
* @param args Set of extra HTML attributes ('''id''' and '''label''' are 2 special arguments).
* @param handler The field constructor.
*/
object checkbox{
def apply(field: play.api.data.Field, args: AttrPair[ text.Builder, _ ]*)(implicit handler: FieldConstructor, lang: play.api.i18n.Lang) =
{
val boxValue = args.find( _.a == attrs.value ).map( _.v ).getOrElse("true").toString
formInput( field, args ){
(id: String, name: String, value: Option[ String ], attributes: Seq[ AttrPair[ text.Builder, _ ] ] ) => {
span(
input(
attrs.`type` := "checkbox",
attrs.id := id,
attrs.name := name,
attrs.value := boxValue,
attrs.checked := (value == Some(boxValue))
)( attributes.filter( _.a != attrs.value ) ),
span( args.find( _.a == fieldAttrs.text ).map( _.v.toString ).getOrElse( "" ).toString ) //TODo
)
}
}
}
}
/**
* Generate an HTML radio group
*
* Example:
* {{{
* inputRadioGroup(
* contactForm("gender"),
* options = Seq("M"->"Male","F"->"Female"),
* fieldAttrs.label := "Gender",
* fieldAttrs.error := contactForm("gender").error.map(_.withMessage("select gender"))
* )
*
* }}}
*
* @param field The form field.
* @param options List of options in radio group
* @param args Set of extra HTML attributes.
* @param handler The field constructor.
*/
object inputRadioGroup{
def apply(field: play.api.data.Field, options: Seq[(String,String)], args: AttrPair[text.Builder,_]* )(implicit handler: FieldConstructor, lang: play.api.i18n.Lang)
{
formInput( field, args ){
(id: String, name: String, value: Option[ String ], attributes: Seq[ AttrPair[ text.Builder, _ ] ] ) => {
span( attrs.cls := "buttonset", attrs.id := id )(
options.map( op => {
val radioButtonId = s"${id}_${op._1}"
val radioButtonName = op._2
span(
input(
attrs.`type` := "radio",
attrs.id := radioButtonId,
attrs.name := name,
attrs.value := radioButtonName,
attrs.checked:= value == Some(op._1), attributes ),
label( attrs.`for` := radioButtonId )( radioButtonName )
)
}
)
)
}
}
}
}
/**
* Generate an HTML input text.
*
* Example:
* {{{
* inputText(field = myForm("name"), attrs.size := 10, attrs.placeholder := "Your name")
* }}}
*
* @param field The form field.
* @param args Set of extra attributes.
* @param handler The field constructor.
*/
object inputText{
def apply(field: play.api.data.Field, args: AttrPair[ text.Builder, _ ]*)(implicit handler: FieldConstructor, lang: play.api.i18n.Lang)
=
{
val inputType = args.find( _.a == attrs.`type` ).map( _.v.toString ).getOrElse( "text" )
formInput( field, args.filter( _.a != attrs.`type` ) ){
(id: String, name: String, value: Option[ String ], attributes: Seq[ AttrPair[ text.Builder, _ ] ] ) => {
input(
attrs.`type` := inputType,
attrs.id := id,
attrs.name := name,
attrs.value := value.getOrElse( "" )
)( attributes )
}
}
}
}
/**
* Generate an HTML input file.
*
* Example:
* {{{
* inputFile(field = myForm("name"), attrs.size:=10)
* }}}
*
* @param field The form field.
* @param args Set of extra attributes.
* @param handler The field constructor.
*/
object inputFile{
def apply(field: play.api.data.Field, args: AttrPair[ text.Builder, _ ]*)(implicit handler: FieldConstructor, lang: play.api.i18n.Lang)
{
formInput( field, args ){
(id: String, name: String, value: Option[ String ], attributes: Seq[ AttrPair[ text.Builder, _ ] ] ) => {
input(
attrs.`type` := "file",
attrs.id := id ,
attrs.name := name
)( attributes )
}
}
}
}
/**
* Generate an HTML5 input date.
*
* Example:
* {{{
* inputDate(field = myForm("releaseDate"), attrs.size := 10)
* }}}
*
* @param field The form field.
* @param args Set of extra attributes.
* @param handler The field constructor.
*/
object inputDate{
def apply(field: play.api.data.Field, args: AttrPair[ text.Builder, _ ]*)(implicit handler: FieldConstructor, lang: play.api.i18n.Lang) = {
formInput( field, args ){
(id: String, name: String, value: Option[ String ], attributes: Seq[ AttrPair[ text.Builder, _ ] ] ) => {
input(
attrs.`type` := "date",
attrs.id := id ,
attrs.name := name,
attrs.value := value.getOrElse( "" )
)( attributes )
}
}
}
}
/**
* Generate an HTML input password.
*
* Example:
* {{{
* inputPassword(field = myForm("password"), attrs.size := 10)
* }}}
*
* @param field The form field.
* @param args Set of extra attributes.
* @param handler The field constructor.
*/
object inputPassword{
def apply(field: play.api.data.Field, args: AttrPair[ text.Builder, _ ]*)(implicit handler: FieldConstructor, lang: play.api.i18n.Lang) =
{
formInput( field, args ){
(id: String, name: String, value: Option[ String ], attributes: Seq[ AttrPair[ text.Builder, _ ] ] ) => {
input(
attrs.`type` := "password",
attrs.id := id ,
attrs.name := name
)( attributes )
}
}
}
}
/**
* Generate an HTML textarea.
*
* Example:
* {{{
* Textarea(field = myForm("address"), attrs.rows := 3, attrs.cols := 50)
* }}}
*
* @param field The form field.
* @param args Set of extra attributes.
* @param handler The field constructor.
*/
object TextArea{
def apply(field: play.api.data.Field, args: AttrPair[ text.Builder, _ ]*)(implicit handler: FieldConstructor, lang: play.api.i18n.Lang) =
{
formInput( field, args ){
(id: String, name: String, value: Option[ String ], attributes: Seq[ AttrPair[ text.Builder, _ ] ] ) => {
textarea(
attrs.id := id ,
attrs.name := name
)( attributes )( value )
}
}
}
}
/**
* Generate an HTML select.
*
* Example:
* {{{
* selectList(field = myForm("isDone"), options = options(List("Yes","No")))
* }}}
*
* @param field The form field.
* @param options Sequence of options as pairs of value and HTML.
* @param args Set of extra attributes ('''_default''' is a special argument).
* @param handler The field constructor.
*/
object selectList{
def apply(
field: play.api.data.Field,
options: Seq[(String,String)],
args: AttrPair[ text.Builder, _ ]*
)(
implicit handler: FieldConstructor, lang: play.api.i18n.Lang
) = {
formInput( field, args ){
(id: String, name: String, value: Option[ String ], attributes: Seq[ AttrPair[ text.Builder, _ ] ] ) => {
val multiple = attributes.find( _.a == attrs.multiple ).map( _.v ) match{
case Some( true ) => true
case _ => false
}
val selectName = name + ( if( multiple ) "[]" else "" )
val values = ( !field.indexes.isEmpty && multiple ) match {
case true => field.indexes.map( i => field("[%s]".format(i)).value ).flatten.toSet
case _ => field.value.toSet
}
select( attrs.id := id, attrs.name := selectName )( attributes )(
args.find( _.a == fieldAttrs.default ).map( _.v.toString ) map { defaultValue =>
option( attrs.cls := "blank", attrs.value := "" )( defaultValue )
},
options.map { v =>
option( attrs.value := v._1, attrs.selected := values.contains( v._1 ) )( v._2 )
}
)
}
}
}
}
/**
* Prepare a generic HTML input.
*/
object formInput{
def apply(
field: play.api.data.Field,
args: Seq[ AttrPair[ text.Builder, _ ] ]
)(
inputDef: (String, String, Option[String], Seq[ AttrPair[text.Builder,_] ] ) => TypedTag[String]
)(
implicit handler: FieldConstructor, lang: play.api.i18n.Lang
) =
{
val id = args.find( _.a == attrs.id ).map( _.v.toString ).getOrElse(field.id)
handler(
FieldElements(
id,
field,
inputDef(
id,
field.name,
field.value,
args.filter( arg => arg.a != attrs.id && !fieldAttrs.All.contains( arg.a ) )
),
args,
lang
)
)
}
}
case class FieldElements(id: String, field: play.api.data.Field, input: TypedTag[String], args: Seq[AttrPair[ text.Builder, _ ]], lang: play.api.i18n.Lang)
{
def infos(implicit lang: play.api.i18n.Lang): Seq[String] = {
args.find( _.a == fieldAttrs.help ).map( _.v ).map(m => Seq(m.toString)).getOrElse {
(if (args.find( _.a == fieldAttrs.showConstraints ).map( _.v.asInstanceOf[Boolean] ) match {
case Some(false) => false
case _ => true
}) {
field.constraints.map(c => play.api.i18n.Messages(c._1, c._2: _*)) ++
field.format.map(f => play.api.i18n.Messages(f._1, f._2: _*))
} else Nil)
}
}
def errors(implicit lang: play.api.i18n.Lang): Seq[String] = {
(args.find( _.a == fieldAttrs.error ).map( _.v ) match {
case Some(Some(play.api.data.FormError(_, message, formErrorArgs ))) => Some(Seq(play.api.i18n.Messages(message, formErrorArgs: _*)))
case _ => None
}).getOrElse {
(if (args.find( _.a == fieldAttrs.showErrors ).map( _.v ) match {
case Some(false) => false
case _ => true
}) {
field.errors.map(e => play.api.i18n.Messages(e.message, e.args: _*))
} else Nil)
}
}
def hasErrors: Boolean = !errors.isEmpty
def label(implicit lang: play.api.i18n.Lang): String = {
args.find( _.a == fieldAttrs.label ).map( _.v.toString ).getOrElse(play.api.i18n.Messages(field.name))
}
}
object repeat {
/**
* Render a field a repeated number of times.
*
* Useful for repeated fields in forms.
*
* @param field The field to repeat.
* @param min The minimum number of times the field should be repeated.
* @param fieldRenderer A function to render the field.
* @return The sequence of rendered fields.
*/
def apply(field: play.api.data.Field, min: Int = 1)(fieldRenderer: play.api.data.Field => TypedTag[String] ): immutable.IndexedSeq[TypedTag[String]] = {
val indexes = field.indexes match {
case Nil => 0 until min
case complete if complete.size >= min => field.indexes
case partial =>
// We don't have enough elements, append indexes starting from the largest
val start = field.indexes.max + 1
val needed = min - field.indexes.size
field.indexes ++ (start until (start + needed))
}
indexes.map(i => fieldRenderer(field("[" + i + "]"))).toIndexedSeq
}
}
object options {
def apply(options: (String, String)*) = options.toSeq
def apply(options: Map[String, String]) = options.toSeq
def apply(options: java.util.Map[String, String]) = options.asScala.toSeq
def apply(options: List[String]) = options.map(v => v -> v)
def apply(options: java.util.List[String]) = options.asScala.map(v => v -> v)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment