Created
February 6, 2015 22:00
-
-
Save SRGOM/9a711b56d37d0ae60dc5 to your computer and use it in GitHub Desktop.
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 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