如何获取 thymeleaf select 选项中的对象?

How can get the object in thymeleaf select option?

在自动完成中,我得到了预期的产品名称。

我想根据所选产品做一些计算。但是在 doCalculation 函数中,我得到的是 id 而不是 'price'。所以计算没有按预期工作。

假设如果我更改 String idExpression = "#{price}";,则计算会按预期进行,但订单未保存。由于出现如下错误

Failed to convert property value of type [java.lang.String] to required type [com.myapp.domain.Product] for property product; nested exception is 
org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@javax.persistence.OneToOne 
 @io.springlets.format.EntityFormat com.myapp.domain.Product] for value 2500; nested exception is java.lang.IllegalStateException: Parsers are not allowed to return null: io.springlets.format.EntityParser@2201ba1c

所以我想在获取价格的同时进行计算,保存功能不应该被破坏。现在第一个或第二个对我有用。

ProductsCollectionThymeleafController.java

@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE, name = "select2", value = "/s2")
@ResponseBody
public ResponseEntity<Select2DataSupport<Product>> select2(GlobalSearch search, Pageable pageable,
        Locale locale) {
    Page<Product> products = getProductService().findAll(search, pageable);
    String idExpression = "#{id}";
    Select2DataSupport<Product> select2Data =
            new Select2DataWithConversion<Product>(products, idExpression, getConversionService());
    return ResponseEntity.ok(select2Data);
}

OrderCollectionThymeleafController.java

@PostMapping(name = "create")
public ModelAndView create(@Valid @ModelAttribute Order order, BindingResult result,
        Model model) {
    if (result.hasErrors()) {
        populateForm(model);
        return new ModelAndView("/order/create");
    }

    Order newOrder = getOrderService().save(order);
    UriComponents showURI = getItemLink().to(OrderItemThymeleafLinkFactory.SHOW)
            .with("order", newOrder.getId()).toUri();
    return new ModelAndView("redirect:" + showURI.toUriString());
}

orderview.html

<form class="form-horizontal validate" method="POST" data-th-object="${order}" data-th-action="@{${collectionLink.to('create').with('order', order.id)}}">
    <fieldset id="containerFields">
        <div class="form-group has-error has-feedback" data-z="3c00987d" id="servicio-product-field" data-th-classappend="${#fields.hasErrors('product')}? 'has-error has-feedback'" data-th-class="form-group" data-th-with="collectionLink=${@linkBuilder.of('ProductsCollectionThymeleafController')}">
            <label for="product" class="col-md-3 control-label" data-th-text="#{label_servicio_product}">Product</label>
            <div class="col-md-6">
                <!-- Select2 -->
                <select data-th-field="*{product}" onChange="doCalculation()" class="form-control dropdown-select-ajax" data-allow-clear="true" data-data-ajax--url="${collectionLink.to('select2')}" data-ajax--cache="true" data-ajax--delay="250" data-ajax--data-type="json" data-data-placeholder="#{info_select_an_option}">
                    <option data-th-unless="*{product} == null" data-th-value="*{product.id}" data-th-text="*{{product}}" selected="selected">Product</option>
                </select>
                <span data-th-classappend="${#fields.hasErrors('product')}? 'glyphicon glyphicon-remove form-control-feedback'" class="glyphicon glyphicon-remove form-control-feedback" data-th-if="${#fields.hasErrors('product')}" aria-hidden="true"></span>
                <span id="product-error" class="help-block" data-th-if="${#fields.hasErrors('product')}" data-th-errors="*{product}">Error message.</span>
            </div>
        </div>
        <script>
            function doCalculation() {
                var price = document.getElementById("product").value;
                alert("price: " + price);
                // Do some calculation
            }
            doCalculation();
        </script>
    </fieldset>
</form>

Product.java

@RooJavaBean
@RooToString
@RooJpaEntity
@RooEquals(isJpaEntity = true)
@Entity
@EntityFormat
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String productName;

    @Min(1L)
    @NumberFormat
    private Integer price;

    @OneToOne(fetch = FetchType.LAZY)
    @EntityFormat
    private Order order;

    public static final String ITERABLE_TO_ADD_CANT_BE_NULL_MESSAGE = "The given Iterable of items to add can't be null!";

    public static final String ITERABLE_TO_REMOVE_CANT_BE_NULL_MESSAGE = "The given Iterable of items to add can't be null!";

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Integer getPrice() {
        return this.price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public String getProductName() {
        return this.productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public Order getOrder() {
        return this.order;
    }

    public void setOrder(Order order) {
        this.order= order;
    }
}

Order.java

@RooJavaBean
@RooToString
@RooJpaEntity
@RooEquals(isJpaEntity = true)
@Entity
@EntityFormat
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Version
    private Integer version;

    @OneToOne(cascade = { javax.persistence.CascadeType.MERGE,
        javax.persistence.CascadeType.PERSIST }, fetch = FetchType.LAZY, mappedBy = "order")
    @RooJpaRelation(type = JpaRelationType.AGGREGATION)
    @EntityFormat
    private Product product;

    public static final String ITERABLE_TO_ADD_CANT_BE_NULL_MESSAGE = "The given Iterable of items to add can't be null!";

    public static final String ITERABLE_TO_REMOVE_CANT_BE_NULL_MESSAGE = "The given Iterable of items to add can't be null!";

    /**
    * This `equals` implementation is specific for JPA entities and uses the
    * entity identifier for it, following the article in
    * https://vladmihalcea.com/2016/06/06/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
    *
    * @param obj
    * @return Boolean
    */
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        // instanceof is false if the instance is null
        if (!(obj instanceof Order)) {
            return false;
        }
        return getId() != null && Objects.equals(getId(), ((Order) obj).getId());
    }

    /**
    * This `hashCode` implementation is specific for JPA entities and uses a
    * fixed `int` value to be able to identify the entity in collections after
    * a new id is assigned to the entity, following the article in
    * https://vladmihalcea.com/2016/06/06/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
    *
    * @return Integer
    */
    public int hashCode() {
        return 31;
    }

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Integer getVersion() {
        return this.version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public Product getProduct() {
        return this.product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    public void addToProduct(Product product) {
        if (product == null) {
            removeFromProduct();
        } else {
            this.product = product;
            product.setOrder(this);
        }
    }

    public void removeFromProduct() {
        if (this.product != null) {
            product.setOrder(null);
        }
        this.product = null;
    }
}

默认情况下,Select2DataWithConversion 数据类型仅 returns 将设置为 option 元素的 value 属性的标识符和对象的表示(在您的例子中是产品名称)作为 option 元素的 text 属性。

这是构建select2组件所需的最少信息。

https://select2.org/data-sources/formats

但是,正如您在回答中所述,在 Select2 组件中需要更多信息是很常见的。出于这个原因,我们重载了 Select2DataWithConversion 的构造函数,包括一个布尔参数 return 对象的全部信息。

在这里检查这个重载的构造函数:

https://github.com/DISID/springlets/blob/master/springlets-data/springlets-data-commons/src/main/java/io/springlets/data/web/select2/Select2DataWithConversion.java#L76

所以,你只需要改变你的ProductsCollectionThymeleafController.java来使用它:

Select2DataSupport<Product> select2Data = new Select2DataWithConversion<Product>(products, idExpression, getConversionService(), true);

现在您的 select2 组件将接收额外信息,您需要在选项创建期间将其存储在 select2 选项的 data-* 属性中。为此,请使用提供 select2 组件的 templateSelection 函数。

https://select2.org/programmatic-control/retrieving-selections#using-a-jquery-selector

现在,您的 doCalculation 应该获得所选的选项,然后是 data-price 属性。

   <script>
    function doCalculation() {
     var price = $('#product').find(':selected').data('price');
     alert("price: " + price);
     //Do some calculation                   
    }
    doCalculation();
   </script>

仅此而已!

编辑:我刚刚创建了以下项目,您可以在其中找到您想要的行为:https://github.com/jcagarcia/proofs/tree/master/select2-with-extra-info 只需检查以下提交中的必要更改:https://github.com/jcagarcia/proofs/commit/105c18f7ad0da4d1e2089fbf71d4f27ccdb60689

希望对您有所帮助,