Сопоставление входных данных select со масштабируемой формой в Play 2.5 — с использованием объекта DTO

#scala #playframework

#scala #playframework

Вопрос:

У меня есть следующий контроллер в Play2.5, взятый из репозитория github raul782 / play-commerce.

 class CategoryController(implicit inj: Injector) extends Controller {

  private val categoryService = inject[CategoryService]

  def viewAllCategories = Action { implicit request =>
    val allCategories = categoryService.findAll.map(Category.toDTO(_))
    Ok(views.html.product.categoryList(allCategories))
  }

  def listCategories = Action { implicit request =>
    val allCategories = categoryService.findAll.map(Category.toDTO(_))
    Ok(RequestUtil.toJsonString(allCategories)).as(JSON)
  }

  def viewCategoryForm(categoryId: Long) = Action { implicit request =>
    Ok(views.html.product.category(views.forms.product.categoryForm,      categoryService.findAll))
  }

  def addCategoryForm = Action { implicit request =>
    Ok(views.html.product.category(views.forms.product.categoryForm,   categoryService.findAll))
  }

  def addCategory = Action { implicit request =>
    import views.forms.product.categoryForm
    categoryForm.bindFromRequest.fold(
      formWithErrors => {
        Ok(views.html.product.category(formWithErrors, categoryService.findAll))
    },
      categoryDTO => {
        play.Logger.debug(request.body.asFormUrlEncoded.toString)
        play.Logger.debug(categoryDTO.toString)
        val category = Category.fromDTO(categoryDTO)
        categoryService.save(category)

        Redirect(routes.CategoryController.viewAllCategories())
      }
    )
  }
}
  

У меня есть настройка category.html.scala как:

 @(categoryForm: Form[dtos.product.CategoryDTO], categoryDTOs:     Seq[models.product.Category])(implicit request:     play.api.mvc.Request[play.api.mvc.AnyContent])

@import models.product.Category
@import helper._
@import views.html.forms.inputHidden
@import views.forms.FormHelpers.bootstrapVerticalFormFieldConstructor

@title = @{categoryForm(Category.Id).value.
                    map(_ => Messages("forms.categories.update")).
                    getOrElse(Messages("forms.categories.add"))}

@action = @{categoryForm(Category.Id).value.
                    map(_ =>     controllers.product.routes.CategoryController.updateCategory()).
                        getOrElse(controllers.product.routes.CategoryController.addCategory())}

@categoryOptions = @{categoryDTOs.map(category => category.id.toString -> category.name)}

@main(title = title) {
<div class="col-sm-2">
    <h2>@title</h2>
</div>
<div class="col-sm-7">
    @form(action = action, args = 'class -> "well") {
        @inputHidden(categoryForm(Category.Id))
        @inputText(categoryForm(Category.Name), 'class -> "form-control")
        @select(categoryForm(Category.ParentCategory), categoryOptions,      'class -> "form-control", '_default -> "--- select ---")
        <div class="form-group">
            <button type="submit" class="btn btn-primary">               
                @Messages("forms.save")
            </button>
            <a href="@controllers.product.routes.CategoryController.viewAllCategories()" class="btn btn-default">@Messages("forms.cancel")</a>
        </div>
    }
</div>
}
  

Мое отображение формы построено следующим образом:

 package views.forms

import play.api.data._
import play.api.data.Forms._
import dtos.product.CategoryDTO

package object product {
  val categoryForm = Form(
    mapping(
      "id" -> optional(longNumber(strict = true)),
      "name" -> text(minLength = 1, maxLength = 255),
      "parentCategoryId" -> list(optional(longNumber))
    ) (CategoryDTO.apply) (CategoryDTO.unapply)
  )
}
  

Мой класс CategoryDTO выглядит следующим образом:

 package dtos.product

case class CategoryDTO(id: Option[Long], name: String, parentCategoryId: List[Option[Long]]) {
  var ancestry: Seq[CategoryDTO] = Nil
  var parentCategoryName: Option[String] = None
}
  

Моя модель категории выглядит следующим образом:

 package models.product

import models.BaseEntity
import javax.persistence.Entity
import javax.persistence.Column
import javax.persistence.Access
import javax.persistence.AccessType
import java.util.LinkedHashSet
import javax.persistence.ManyToOne
import javax.persistence.OneToMany
import java.util.{Set => JSet}
import models.IdentifierProperty
import dtos.product.CategoryDTO

@Entity
class Category extends BaseEntity {

  def this(id: Option[Long]) = {
   this()
   this.id = id
  }

  @Column(nullable = false, length = 255)
  var name: String = _

  @Access(AccessType.PROPERTY)
  @ManyToOne(targetEntity = classOf[Category])
  var parentCategory: Option[Category] = None

  @OneToMany(mappedBy = "parentCategory")
  var childCategories: JSet[Category] = new LinkedHashSet

  def ancestors = {
    traverseAncestry(parentCategory)  
  }

  private def traverseAncestry(parentCategory: Option[Category], lst: List[Category] = Nil): Seq[Category] = {
    parentCategory  match {
      case Some(currentParentCategory) => traverseAncestry(currentParentCategory.parentCategory, currentParentCategory :: lst)
      case _ => lst
    }
  }

  protected def getParentCategory() = {
    parentCategory.getOrElse(null)
  }

  protected def setParentCategory(category: Category) {
    parentCategory = Option(category)
  }
}

object Category extends IdentifierProperty {
  val Name = "name"
  val ParentCategory = "parentCategoryId"
  val ChildCategories = "childCategories"

  def toDTO(category: Category, withAncestry: Boolean = false): CategoryDTO =   {
    val dto = CategoryDTO(category.id, category.name, category.parentCategory.map( c => c.id).toList)
    dto.parentCategoryName = category.parentCategory.map(_.name)
    dto.ancestry = if(withAncestry) { 
      category.ancestors.map(Category.toDTO(_)) 
    } else { 
      Nil
    }
    dto
  }

  def fromDTO(dto: CategoryDTO) = {
    val category = new Category
    category.name = dto.name
    category.parentCategory = dto.parentCategoryId match {
      case Nil => None
      case head::_ => head.map(id => new Category(Some(id)))
    }
    category
  }
}
  

Когда я заполняю форму и отправляю запрос, форма сохраняется, но только имя, но не родительская категория.
Я заметил, что отправленное входное значение select равно некоторому (15), где 15 — это ParentCategoryID.

Изначально сопоставление categoryForm для ParentCategoryID было просто необязательным (longNumber), но я также не смог зафиксировать значение.

Я переключился на list (необязательно (longNumber)), потому что именно так это выглядит в документах, когда вы используете ввод select.

Но по-прежнему безуспешно, какие-либо советы или что может быть не так с моей реализацией?

Спасибо

Ответ №1:

Судя по вашему вопросу и тому факту, что категория имеет только одно название родительской категории, я предполагаю, что вам нужен только (единственный) идентификатор родительской категории? Это довольно типичная ситуация с «внешним ключом». select Элемент выбирает (по умолчанию) только один элемент.

  • При сопоставлении используйте optional(longNumber) , а не список.
  • В dto сделайте это Option[Int] , а не коллекцией.

Если вам нужен список родительских категорий, то ваш текущий код ближе к тому, что вам нужно, но select сам по себе этого не сделает — вам понадобится библиотека javascript с несколькими вариантами выбора.

P.S. Есть ли причина, по которой вы используете var, а не val?