简体   繁体   中英

Play Framework 2 - Scala template: How to call a specific method on a generic object?

I am using Play Framework 2.1.5 in a Java application.

I've got a view component that takes a list of generic objects as a parameter. In this component, I want to iterate on the list and get some properties of each element.

This would look like something like this:

@(elements: List[_])

@for((element, i) <- elements.view.zipWithIndex) {
    @i
    @element.id
    @element.name
}

(I need those 3 values)

But, of course, element.id and element.name would not compile even if the type of objects I put in the list contained these methods. So I did this:

@for((element, i) <- elements.view.zipWithIndex) {
    @defining(
        ViewsUtils.getGenericElementId(element),
        ViewsUtils.getGenericElementName(element)) {
            case (id, name, something) =>
                @i
                @id
                @name
        }
}

And in a Java utility class:

public final class ViewsUtils {

    public static String getGenericElementId(Object object) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return object.getClass().getMethod("getId").invoke(object).toString();
    }

    public static String getGenericElementName(Object object) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return object.getClass().getMethod("getName").invoke(object).toString();
    }
}

This works but I know it's not right because it could throw a RuntimeException in case one of these methods didn't exist for the type of object I put in the list.

Here are my suppositons:

  1. Casting the elements

  2. Using inheritance

  3. As I just need 2 properties of each object (id and name), I could use a map, but I need the index of the loop (is it possible to get it from a map?) and it wouldn't work if I needed more than 2 properties.

  4. Maybe Scala provides the syntax for this kind of stuff.

Or maybe I'm just looking in the wrong direction.

Thank you for your help.

Ok, that is a bit too much for a comment, so I'll risk to post it as an answer.

Assuming you wish to never get a RuntimeException you mentioned it makes sense that your object implement some interface (or mix in a trait, as it is said in Scala), so you won't risk getting an exception and no more need reflection to get values.

Suppose, you declare such a trait:

trait GenericObject {
  val id: Long
  val name: String
}

Then you declare some case classes:

case class A(id: Long, name: String, someOtherField: SomeType) extends GenericObject {
  //your implementation
}

case class B(id: Long, name: String) extends GenericObject

case class C(id: Long, name: String) extends B(id, name)

Now you can change your template like this:

@(elements: List[GenericObject])

@for((element, i) <- elements.view.zipWithIndex) {
  @i
  @element.id
  @element.name
}

And you should pass the list of GenericObject s to your template:

val myObjects: List[GenericObject] = List(A(1, "A name"), B(2, "B name"), C(3, "C name"))
Ok(your_template.scala.html(myObjects))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM