Bozhidar Batsov
Bozhidar Batsov

Reputation: 56605

How can you dynamically generate input forms in Lift

I'm working on my first Lift project and have the need to generate forms on the fly. To be more precise I have some operations which are represented by objects that have a list of parameters attached to them - some of them are strings, some numbers, some boolean, some files. I need to feed an operation object concrete values for its parameters via a form. Currently I simply traverse the parameter list and generate the form directly as XML, but I know this is extremely bad style - I cannot bind the input values, I cannot bind an action to submit, I cannot apply validation. I'm stuck passing around request params, but I want to fix this. Unfortunately I have to idea how to do this, so any help will be greatly appreciated. All the example I keep finding have always a fixed number of input parameters upfront.

Here's a bit of related code. I hope it's understandable enough(and I'm really ashamed to publicly show it :-) )

def operationForm(): NodeSeq = {
    val operationCode = S.param("operation").openOr("")
    val operationVariant = S.param("operationVariant").openOr("")

    if (operationCode != "" && !operationVariant.isEmpty) {
      val operation = LighthouseDAOs.operationsRegistry.findByCode(operationCode)
      val params: List[Parameter] = if (operationVariant == "default") {
        operation.getParameters.toList
      } else {
        operation.getParameters.filter(p => p.getVariant == operationVariant).toList
      }

      <lift:surround with="closableBox" at="content">
        <form id="viewOperation" post={"/Deployment/" + S.param("location") + "/" + S.param("deployment")} method="post">
          {params.map(p => getInputElem(p))}
            <input type="submit" style="width: 150px;" value="Execute operation"/>
            <input type="hidden" name="executeOperation" value="true"/>
        </form>
      </lift:surround>
    } else {
      <span></span>
    }
  }

  private def getOperationVariants(operation: Operation): Set[String] = {
    operation.getParameters.map(_.getVariant).toSet
  }

  def operationVariants(deployment: Deployment): NodeSeq = {
    val operationCode = S.param("operation").openOr("")

    if (operationCode != "") {
      val operation = LighthouseDAOs.operationsRegistry.findByCode(operationCode)

      val variants = getOperationVariants(operation)

      if (variants.size > 1) {
        <lift:surround with="closableBox" at="content">
          <table cellspacing="0" cellpadding="0" border="0">
            <tr>
              <th style="width: 160px;">Operation
                {operation.getLongName}
                variants</th>
            </tr>{variants.map(v => {
            <tr>
              <td>
                <a href={Path.path + "Deployment/" + encode(deployment.getLocation) + "/" + encode(deployment.getDeployedComponent.getCode) + "?operation=" + encode(operation.getCode) + "&operationVariant=" + encode(v)}>
                  {v}
                </a>
              </td>
            </tr>
          })}
          </table>
        </lift:surround>
      } else {
        <span></span>
      }
    } else {
      <span></span>
    }
  }

  def getInputElem(param: Parameter): Node = {
    if (param.getChoice != null) {
      <div>
        <label for={param.getName}>
          {param.getName}
        </label>
        <select id={param.getName} name={param.getName}>
          {param.getChoice.flatMap(c => <option value={c}>
          {c}
        </option>)}
        </select>
      </div>
    } else {

      val paramType = param.getType match {
        case Parameter.PASSWORD => "password"
        case Parameter.BOOLEAN => "checkbox"
        case Parameter.CLOB => "file"
        case _ => "text"
      }

      <div>
        <label for={param.getName}>
          {param.getName}
          :</label> <input type={paramType} id={param.getName} name={param.getName} stype="width: 300px;">
        {param.getDefaultValue}
      </input>
      </div>
    }
  }

  def executeOperation(deployment: Deployment): Elem = {
    val operationCode = S.param("operation").openOr("")
    val operationVariant = S.param("operationVariant").openOr("")

    if (S.param("executeOperation").openOr("false") == "true") {
      val op = LighthouseDAOs.operationsRegistry.findByCode(operationCode)
      val params = op.getParameters

      LogLH3.info("Executing operation: " + op.getLongName)

      val operationInstallation = new OperationInstallation();
      operationInstallation.setInstallationLocation(deployment);
      operationInstallation.setInstalledOperation(op);

      val operationCall = new OperationCall(operationInstallation);
      if (operationVariant != "" && operationVariant != "default")
        operationCall.setVariant(operationVariant)

      params.filter(p => p.getVariant == operationVariant).foreach(p => operationCall.addParameterValue(op.createParameterValue(p.getName, S.param(p.getName).openOr(""))))

      try {
        LighthouseDAOs.operationInstallationRegistry.execute(operationCall)
        S.notice("Operation " + op.getLongName + " was executed successfully.")
      } catch {
        case e: Exception => S.error(e.getMessage)
      }
    }

    <span></span>
  }

Just for the record I'm using Lift 2.2 & Scala 2.8.1

Upvotes: 4

Views: 1144

Answers (1)

Joa Ebert
Joa Ebert

Reputation: 6715

You could improve this example and make it a little bit more designer friendly but here is the basic idea. You will need to wrap this in some kind of form of course.

Basically you have a template and replace its contents with something dynamically generated using CSS selector binding.

In your template:

<div class="lift:MyForm.render">
  Form will go here
</div>

The snippet:

class MyForm extends Snippet {
  def render = {
    val fields = List("a", "b", "c")
    var params: Map[String, String] = ... //or whatever

    def setf(key: String)(value: String) = params = params.updated(key, value)
    def getf(key: String)() = params.get(key)

    "*" #> fields map {
      field =>
        <p>
          <label>{field}</label>{SHtml.text(getf(field) _, setf(field) _)}
        </p>
    }
  }
}

Upvotes: 3

Related Questions