All Downloads are FREE. Search and download functionalities are using the official Maven repository.

views.bs.package.scala Maven / Gradle / Ivy

/**
 * Copyright 2015 Adrian Hurtado (adrianhurt)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package views.html

package object bs {

  import play.api.data.{ Field, FormError }
  import play.twirl.api.Html
  import play.api.i18n.{ Lang, Messages }
  import bs.ArgsMap.isTrue
  import play.api.mvc.Call

  /**
   * Class with relevant variables for a field to pass it to the helper and field constructor
   * - args: list of available arguments for the helper and field constructor
   */
  class BSFieldInfo(field: Field, args: Seq[(Symbol, Any)], messages: Messages) {

    /* A map with the args to work easily with them */
    val argsMap: Map[Symbol, Any] = Args.withoutNones(args).toMap

    /* Id of the input */
    val id: String = argsMap.get('id).map(_.toString).getOrElse(field.id)

    /* Id of the form-group */
    val idFormField: String = argsMap.get('_id).map(_.toString).getOrElse(id + "_field")

    /* The optional label */
    val labelOpt: Option[Any] = argsMap.get('_label).orElse(argsMap.get('_hiddenLabel))

    /* Indicates if the label must be hidden */
    val hideLabel: Boolean = isTrue(argsMap, '_hideLabel) || argsMap.contains('_hiddenLabel)

    /* Name of the input */
    def name: String = field.name

    /* Value of the input */
    val value: Option[String] = field.value.orElse(argsMap.get('value).map(_.toString))

    /* List with every "info" and its corresponding ARIA id. Ex: ("foo_info_0" -> "foo constraint")  */
    val infos: Seq[(String, String)] = BSFieldInfo.infos(Some(field), argsMap, messages).zipWithIndex.map {
      case (info, i) => (ariaInfoId(i), info)
    }

    /* List with every error and its corresponding ARIA id. Ex: ("foo_error_0" -> "foo error")  */
    val errors: Seq[(String, String)] = BSFieldInfo.errors(Some(field), argsMap, messages).zipWithIndex.map {
      case (error, i) => (ariaErrorId(i), error)
    }

    /* List with the errors and infos */
    def errorsAndInfos = errors ++ infos

    /* Indicates if there is any error */
    val hasErrors: Boolean = !errors.isEmpty || ArgsMap.isNotFalse(argsMap, '_error)

    /* The optional validation state ("success", "warning" or "error") */
    lazy val status: Option[String] = BSFieldInfo.status(hasErrors, argsMap)

    /* ARIA id for an "info" given an index (ex: "foo_info_0") */
    def ariaInfoId(index: Int): String = id + "_info_" + index

    /* ARIA id for an error given an index (ex: "foo_error_0") */
    def ariaErrorId(index: Int): String = id + "_error_" + index

    /* List of every ARIA id */
    val ariaIds: Seq[String] = infos.map(_._1) ++ errors.map(_._1)

    /*
    * Map with the inner args, i.e. those args for the helper itself removing those ones reserved for the field constructor.
    * It adds the ARIA attributes and removes the underscored reserved for the field constructor and the `id and `value ones that are
    * managed independently.
    */
    lazy val innerArgsMap: Map[Symbol, Any] = (
      (if (ariaIds.size > 0) Seq(Symbol("aria-describedby") -> ariaIds.mkString(" ")) else Nil) ++
      (if (hasErrors) Seq(Symbol("aria-invalid") -> "true") else Nil) ++
      BSFieldInfo.constraintsArgs(field, messages) ++
      args.filterNot { case (key, _) => key == 'id || key == 'value || key.name.startsWith("_") }
    ).toMap.filterNot { case (_, value) => value == false }
  }

  /**
   * Companion object for class BSFieldInfo
   */
  object BSFieldInfo {

    def apply(field: Field, args: Seq[(Symbol, Any)], messages: Messages): BSFieldInfo = {
      new BSFieldInfo(field, args, messages)
    }

    /* List with every "info" */
    def infos(maybeField: Option[Field], argsMap: Map[Symbol, Any], messages: Messages): Seq[String] = {
      argsMap.get('_warning).filter(!_.isInstanceOf[Boolean]).map(m => Seq(messages(m.toString))).getOrElse(
        argsMap.get('_success).filter(!_.isInstanceOf[Boolean]).map(m => Seq(messages(m.toString))).getOrElse(
          argsMap.get('_help).map(m => Seq(messages(m.toString))).getOrElse {
            maybeField.filter(_ => argsMap.get('_showConstraints) == Some(true)).map { field =>
              field.constraints.map(c => messages(c._1, c._2.map(a => translateMsgArg(a, messages)): _*)) ++ field.format.map(f => messages(f._1, f._2.map(a => translateMsgArg(a, messages)): _*))
            }.getOrElse(Nil)
          }
        )
      )
    }

    /* List with every error */
    def errors(maybeField: Option[Field], argsMap: Map[Symbol, Any], messages: Messages): Seq[String] = {
      argsMap.get('_error).filter(!_.isInstanceOf[Boolean]).map {
        _ match {
          case Some(FormError(_, message, args)) => Seq(messages(message, args.map(a => translateMsgArg(a, messages)): _*))
          case message => Seq(messages(message.toString))
        }
      }.getOrElse {
        maybeField.filter(_ => argsMap.get('_showErrors) != Some(false)).map { field =>
          field.errors.map { e => messages(e.message, e.args.map(a => translateMsgArg(a, messages)): _*) }
        }.getOrElse(Nil)
      }
    }

    /* The optional validation state ("success", "warning" or "error") */
    def status(hasErrors: Boolean, argsMap: Map[Symbol, Any]): Option[String] = {
      if (hasErrors)
        Some("error")
      else if (ArgsMap.isNotFalse(argsMap, '_warning))
        Some("warning")
      else if (ArgsMap.isNotFalse(argsMap, '_success))
        Some("success")
      else
        None
    }

    /* Generates automatically the input attributes for the constraints of a field */
    def constraintsArgs(field: Field, messages: Messages): Seq[(Symbol, Any)] = field.constraints.map {
      case ("constraint.required", params) => Some(('required -> true))
      case ("constraint.min", params: Seq[Any]) => Some(('min -> messages(params.head.toString)))
      case ("constraint.max", params: Seq[Any]) => Some(('max -> messages(params.head.toString)))
      case ("constraint.minLength", params: Seq[Any]) => Some(('minlength -> messages(params.head.toString)))
      case ("constraint.maxLength", params: Seq[Any]) => Some(('maxlength -> messages(params.head.toString)))
      case ("constraint.pattern", params: Seq[Any]) => params.head match {
        case str: String => Some(('pattern -> messages(str)))
        case func: Function0[_] => Some(('pattern -> messages(func.asInstanceOf[() => scala.util.matching.Regex]().toString)))
        case _ => None
      }
      case _ => None
    }.flatten

    private def translateMsgArg(msgArg: Any, messages: Messages) = msgArg match {
      case key: String => messages(key)
      case keys: Seq[_] => keys.map(key => messages(key.toString))
      case _ => msgArg
    }
  }

  /**
   * Class with relevant variables for the global information of a multifield
   * - fields: list of Fields
   * - globalArguments: list of available arguments for the global helper
   * - fieldsArguments: list of available arguments for every specific field
   */
  class BSMultifieldInfo(fields: Seq[Field], globalArguments: Seq[(Symbol, Any)], fieldsArguments: Seq[(Symbol, Any)], messages: Messages) {

    /* A map with the args to work easily with them. The '_help is removed because the helper freeFormFieldormField will add it */
    val argsMap: Map[Symbol, Any] = Args.withoutNones(fieldsArguments ++ globalArguments).toMap

    /* List with every "info" */
    val infos: Seq[String] = {
      val globalInfos = BSFieldInfo.infos(None, argsMap, messages)
      if (globalInfos.size > 0)
        globalInfos
      else
        fields.flatMap { field =>
          BSFieldInfo.infos(Some(field), argsMap, messages)
        }
    }

    /* List with every error */
    val errors: Seq[String] = {
      val globalErrors = BSFieldInfo.errors(None, argsMap, messages)
      if (globalErrors.size > 0)
        globalErrors
      else
        fields.flatMap { field =>
          BSFieldInfo.errors(Some(field), Map(), messages)
        }
    }

    /* List with the errors and infos */
    def errorsAndInfos: Seq[String] = errors ++ infos

    /* Indicates if there is any error */
    val hasErrors: Boolean = !errors.isEmpty || ArgsMap.isNotFalse(argsMap, '_error)

    /* The optional validation state ("success", "warning" or "error") */
    lazy val status: Option[String] = BSFieldInfo.status(hasErrors, argsMap)

    lazy val globalArgs = globalArguments

    lazy val fieldsArgs = fieldsArguments
  }

  /**
   * Companion object for class BSMultifieldInfo
   */
  object BSMultifieldInfo {
    def apply(fields: Seq[Field], globalArguments: Seq[(Symbol, Any)], fieldsArguments: Seq[(Symbol, Any)], messages: Messages): BSMultifieldInfo = {
      new BSMultifieldInfo(fields, globalArguments, fieldsArguments, messages)
    }
  }

  /**
   * Custom BSFieldConstructor for the library. Every BSFieldConstructor must extend this functionality.
   */
  trait BSFieldConstructor[F <: BSFieldInfo] {
    /* Renders the corresponding template of the field constructor */
    def apply(fieldInfo: F, inputHtml: Html): Html
    /* Renders the corresponding template of a fake field constructor (i.e. with the same structure but without the field) */
    def apply(contentHtml: Html, argsMap: Map[Symbol, Any]): Html
  }

  /**
   * Renders an input field with its corresponding wrapper using the BSFieldConstructor.
   * - fieldInfo: a BSFieldInfo with all the information about the field.
   * - inputDef: function that returns a Html from the BSFieldInfo.
   */
  def inputFormField[F <: BSFieldInfo](fieldInfo: F)(inputDef: F => Html)(implicit fc: BSFieldConstructor[F]) =
    fc(fieldInfo, inputDef(fieldInfo))

  /**
   * Renders a fake field constructor using the BSFieldConstructor.
   * - args: list of available arguments for the helper and the form-group
   * - contentDef: function that returns a Html from a map of arguments
   */
  def freeFormField[F <: BSFieldInfo](args: Seq[(Symbol, Any)])(contentDef: Map[Symbol, Any] => Html)(implicit fc: BSFieldConstructor[F]) = {
    val argsWithoutNones = Args.withoutNones(args)
    fc(contentDef(Args.inner(argsWithoutNones).toMap), argsWithoutNones.toMap)
  }

  /**
   * Renders a multi-field constructor using the BSFieldConstructor.
   * - fieldInfo: a BSMultifieldInfo with all the information about the fields.
   * - contentDef: function that returns a Html from the BSMultifieldInfo
   */
  def multifieldFormField[F <: BSFieldInfo, M <: BSMultifieldInfo](multifieldInfo: M)(contentDef: M => Html)(implicit fc: BSFieldConstructor[F]) =
    freeFormField(multifieldInfo.globalArgs)(_ => contentDef(multifieldInfo))(fc)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy