0

I try to show a select field that gets its options from the model.

I tried to do it as described in the following stackoverflow questions:

But I couldn't get it to work.

Given the following model:

public class Model {
    private String field;
    private List<String> options
}

and the following view:

@(modelForm: Form[models.Model])

@helper.select(field = modelForm("field"), options = helper.options(modelForm("options").value()))

The apply method of helper.options has a entry for java.util.List[String] so it should be okay with a java string list.

def apply(options: java.util.List[String]) = options.asScala.map(v => v -> v)

With this I only get the following error:

overloaded method value apply with alternatives:
[error]   (options: java.util.List[String])scala.collection.mutable.Buffer[(String, String)] <and>
[error]   (options: List[String])List[(String, String)] <and>
[error]   (options: java.util.Map[String,String])Seq[(String, String)] <and>
[error]   (options: Map[String,String])Seq[(String, String)] <and>
[error]   (options: (String, String)*)Seq[(String, String)]
[error]  cannot be applied to (String)
[error]             options = helper.options(modelForm("options").value())

If I then additionally cast the form field to java.util.List<String> instead of a compile time exception I get a runtime exception:

@helper.select(field = modelForm("field"), options = helper.options(modelForm("options").value().asInstanceOf[util.List[String]]))

Runtime exception:

[error] ErrorHandler - Exception StackTrace: 
[views.html.modelFormView$.$anonfun$apply$1(modelFormView.template.scala:48), 
views.html.helper.form$.apply(form.template.scala:36), 
views.html.modelFormView$.apply(modelFormView.template.scala:41), 
views.html.modelFormView$.render(modelFormView.template.scala:75), 
views.html.modelFormView.render(modelFormView.template.scala), 
controllers.modelController.requestJwt(modelController.java:71), 
router.Routes$$anonfun$routes$1.$anonfun$applyOrElse$16(Routes.scala:304), 
play.core.routing.HandlerInvokerFactory$$anon$3.resultCall(HandlerInvoker.scala:134), play.core.routing.HandlerInvokerFactory$$anon$3.resultCall(HandlerInvoker.scala:133), play.core.routing.HandlerInvokerFactory$JavaActionInvokerFactory$$anon$8$$anon$2$$anon$1.invocation(HandlerInvoker.scala:108), play.core.j.JavaAction$$anon$1.call(JavaAction.scala:88), 
play.http.DefaultActionCreator$1.call(DefaultActionCreator.java:31), 
play.core.j.JavaAction.$anonfun$apply$8(JavaAction.scala:138), 
scala.concurrent.Future$.$anonfun$apply$1(Future.scala:655), 
scala.util.Success.$anonfun$map$1(Try.scala:251), 
scala.util.Success.map(Try.scala:209), 
scala.concurrent.Future.$anonfun$map$1(Future.scala:289), 
scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:29), 
scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:29), 
scala.concurrent.impl.CallbackRunnable.run$$$capture(Promise.scala:60), 
scala.concurrent.impl.CallbackRunnable.run(Promise.scala), 
play.core.j.HttpExecutionContext$$anon$2.run(HttpExecutionContext.scala:56), 
play.api.libs.streams.Execution$trampoline$.execute(Execution.scala:70), 
play.core.j.HttpExecutionContext.execute(HttpExecutionContext.scala:48), 
scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:68), 
scala.concurrent.impl.Promise$KeptPromise$Kept.onComplete(Promise.scala:368), 
scala.concurrent.impl.Promise$KeptPromise$Kept.onComplete$(Promise.scala:367), 
scala.concurrent.impl.Promise$KeptPromise$Successful.onComplete(Promise.scala:375), 
scala.concurrent.impl.Promise.transform(Promise.scala:29), 
scala.concurrent.impl.Promise.transform$(Promise.scala:27), 
scala.concurrent.impl.Promise$KeptPromise$Successful.transform(Promise.scala:375), 
scala.concurrent.Future.map(Future.scala:289), 
scala.concurrent.Future.map$(Future.scala:289), 
scala.concurrent.impl.Promise$KeptPromise$Successful.map(Promise.scala:375), 
scala.concurrent.Future$.apply(Future.scala:655), 
play.core.j.JavaAction.apply(JavaAction.scala:138), 
play.api.mvc.Action.$anonfun$apply$2(Action.scala:96), 
play.api.libs.streams.StrictAccumulator.$anonfun$mapFuture$4(Accumulator.scala:174), 
scala.util.Try$.apply(Try.scala:209), 
play.api.libs.streams.StrictAccumulator.$anonfun$mapFuture$3(Accumulator.scala:174), 
scala.Function1.$anonfun$andThen$1(Function1.scala:52), 
play.api.libs.streams.StrictAccumulator.run(Accumulator.scala:207), 
play.core.server.AkkaHttpServer.$anonfun$runAction$4(AkkaHttpServer.scala:304), 
akka.http.scaladsl.util.FastFuture$.strictTransform$1(FastFuture.scala:41), 
akka.http.scaladsl.util.FastFuture$.$anonfun$transformWith$3(FastFuture.scala:51), 
scala.concurrent.impl.CallbackRunnable.run$$$capture(Promise.scala:60), 
scala.concurrent.impl.CallbackRunnable.run(Promise.scala), 
akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:55), 
akka.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:91), 
scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12), 
scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:81), 
akka.dispatch.BatchingExecutor$BlockableBatch.run(BatchingExecutor.scala:91), 
akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:40), 
akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(ForkJoinExecutorConfigurator.scala:43), akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260), 
akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339), 
akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979), 
akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)]

I don't get what I'm doing wrong.

Spenhouet
  • 6,556
  • 12
  • 51
  • 76

2 Answers2

2

The documentation tells that helper.select wants a Seq[(String, String)] as options-argument.

In your code, modelForm("options") is a call to the method apply(name: String) on Form, which returns a Form.Field (doc). The class Form.Field has a method value, but it returns a String.

String is not Seq[(String, String)].

You have to pass the Seq[(String, String)] as a separate argument, or construct it in-place.

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • So you are saying that a Form.Field can't contain a list? – Spenhouet Feb 05 '18 at 11:49
  • "Form" represents a form filled with a bunch of values. If you have a list of options, and then click on a single option, you *select* a *single `String`*, not a `List`. Just try to imagine what the encoding of the form submission would look like: it would send a single string value, not a list of strings. Having a `List` inside the object that represents a form doesn't seem to make much sense to me. – Andrey Tyukin Feb 05 '18 at 11:53
  • Sounds logical. Thank you for the explanation – Spenhouet Feb 05 '18 at 11:55
1

Have you tried doing the mapping inside the twirl select object instead?

@helper.select(field = modelForm("field"), options = modelForm("options").asScala.map(v => v -> v).value()))

As these errors:

[error]   (options: (String, String)*)Seq[(String, String)]
[error]  cannot be applied to (play.data.Form.Field)
[error]             options = helper.options(modelForm("options").value())

lead me to believe that it dosen't expect a map at that stage.

airudah
  • 1,169
  • 12
  • 19
  • This fails due to asScala not being a method or value of play.data.Form.Field https://www.playframework.com/documentation/2.6.3/api/java/play/data/Form.Field.html – Spenhouet Feb 05 '18 at 11:47
  • 1
    You'll need to import `scala.collection.JavaConverters._ ` – airudah Feb 05 '18 at 12:24