سؤال

I am totally changing this question, as part of it was answered here with great help of Avnish! Tom sent me to the right direction so thank you Tom!

My problem is that I do not know how to tell Thymeleaf to preselect object elements when editing it.

Let me show you:

looks like this

This solution works:

<select class="form-control" id="parts" name="parts" multiple="multiple">
    <option th:each="part : ${partsAtribute}"
            th:selected="${servisAttribute.parts.contains(part)}"
            th:value="${part.id}"
            th:text="${part.name}">Part name</option>
</select>

I have tried this:

<select class="form-control" th:field="*{parts}" multiple="multiple">
    <option th:each="part : ${partsAtribute}"
            th:field="*{parts}"
            th:value="${part.id}"
            th:text="${part.name}">Part name</option>
</select>

did not work. I also tried this:

<select class="form-control" th:field="*{{parts}}" multiple="multiple">
    <option th:each="part : ${partsAtribute}"
            th:field="*{parts}"
            th:value="${part.id}"
            th:text="${part.name}">Part name</option>
</select>

did not work either. I have tried removing th:field="*{parts}" from the option tag, same result..

If I change th:value to ${part} it works, but then it does not send back string of ids like [2,4,5,6,...], but Part instances like [Part@43b45j, Part@we43y7,...]...

UPDATE: I just noticed that this works if only one part is selected:

<select class="form-control" th:field="*{parts}" multiple="multiple">
    <option th:each="part : ${partsAtribute}"
            th:field="*{parts}"
            th:value="${part.id}"
            th:text="${part.name}">Part name</option>
</select>

If multiple parts are selected, it does not work.

هل كانت مفيدة؟

المحلول

After discussion on the Thymeleaf forum, I implemented a full working example at https://github.com/jmiguelsamper/thymeleafexamples-selectmultiple

I think that the only problem with your final code is that you have to use double bracket syntax to invoke the conversionService:

th:value="${{part}}"

It is also important to implement proper equals() and hashcode() methods in your Part class to assure proper comparison.

I hope my example helps other users with similar problems in the future.

نصائح أخرى

You don't need th:selected when using th:field normally. Thymeleaf will automatically check the values of each <option> in the <select>, even if it is multiple

The problem lies in the value. You are iterating over parts, but the value of each option is part.id. Thus you are comparing instances of part to the id of part (as far as I can see).

However, Thymeleaf also takes into account instances of PropertyEditor (it reuses org.springframework.web.servlet.tags.form.SelectedValueComparator).

This will be used when comparing the objects to the values of the options. It will convert the objects to their text value (their id) and compare this to the value.

<select class="form-control" th:field="*{parts}" multiple="multiple" >
        <option th:each="part : ${partsAttribute}" 
                <!-- 
                    Enable the SpringOptionFieldAttrProcessor .
                    th:field value of option must be equal to that of the select tag
                -->
                th:field="*{parts}" 
                th:value="${part.id}" 
                th:text="${part.name} + ${part.serial}">Part name and serial No.                    
        </option>
</select>

Property Editor

Define a PropertyEditor for the parts. The PropertyEditor will be called when comparing the values, and when binding the parts back to the form.

@Controller
public class PartsController {
    @Autowired
    private VehicleService vehicleService;

    @InitBinder(value="parts")
    protected void initBinder(final WebDataBinder binder) {
        binder.registerCustomEditor(Part.class, new PartPropertyEditor ());
    }

    private static class PartPropertyEditor extends PropertyEditorSupport {
        @Override
        public void setAsText(String partId) {
            final Part part = ...; // Get part based on the id 
            setValue(part);
        }

        /**
         * This is called when checking if an option is selected
         */
        @Override
        public String getAsText() {
           return ((Part)getValue()).getId(); // don't forget null checking
        }
    }
}

Also take a look at ConvertingPropertyEditorAdapter. Converter instances that are registered in the conversionService are more preferred in Spring nowadays.

This works for me:

A vet has many specialties.

Controller:

@RequestMapping(value = "/vets/{vetId}/edit", method = RequestMethod.GET)
public ModelAndView editVet(@PathVariable("vetId") int ownerId/*, Model model*/) {

    ModelAndView mav = new ModelAndView("vets/vetEdit");

    mav.addObject("vet", this.vets.findById(ownerId));

    mav.addObject("allSpecialties", this.specialities.findAll());         

    return mav;     
}

View (using th:selected):

<select id="specialities" class="form-control" multiple>
            <option th:each="s : ${allSpecialties}"                                        
                    th:value="${s.id}"
                    th:text="${s.name}"
                    th:selected="${vet.specialties.contains(s)}">
            </option>
        </select>

View (using th:field):

<form th:object="${vet}" class="form-horizontal" id="add-vet-form" method="post">
    <div class="form-group has-feedback">
        <select th:field="*{specialties}" class="form-control" multiple>
            <option th:each="s : ${allSpecialties}"                                        
                    th:value="${s.id}"
                    th:text="${s.name}"
                   >               
            </option>
        </select>        
    </div>

And I have to define Specialty findOne(@Param("id") Integer id) throws DataAccessException; in the SpecialtyRepository, otherwise the following exception is thrown : "java.lang.IllegalStateException: Repository doesn't have a find-one-method declared!"

package org.springframework.samples.petclinic.vet;

import java.util.Collection;

import org.springframework.dao.DataAccessException;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;

public interface SpecialtyRepository extends Repository<Specialty, Integer> {

    @Transactional(readOnly = true)   
    Collection<Specialty> findAll() throws DataAccessException;

    Specialty findOne(@Param("id") Integer id) throws DataAccessException;
}

Here's how I did it:

  <select  th:field="*{influenceIds}" ID="txtCategoryName" class="m-wrap large" multiple="multiple">
    <option th:each="influence : ${influences}" th:value="${influence.get('id')}" th:text="${influence.get('influence')}" ></option>
 </select>

My DTO contains:

private List<String> influenceIds;
<select id="produtos" name="selectedItens" style="width: 100%;" multiple="multiple" required="">
                <option th:value="${p.id}" th:text="${p}" th:each="p : ${slideShowForm.itens}" th:selected="${#lists.contains(slideShowForm.selectedItens,p)}"></option>
            </select>
<select  th:field="*{groupId}" >
    <option th:each="group :${grouptype}"
            th:value="${{group.groupId}}"
            th:text="${group.Desc}">

    </option>
</select>

Simple select example

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top