#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?