java - Spring MVC conversion HOW TO -
i have vehicle service that, among other has list of parts. adding new service not problem, viewing of service not problem, when try implement edit, not preselects list of parts. so, thinking thymeleaf issue, post question here.
and answer got try implement spring conversion service. did (i think), , need me out of mess. problem view compares instances of parts service instances of parts form partsattribute containing parts, , never uses converters, not work. receive no errors... in view, parts not selected. bellow find converters, webmvcconfig, partrepository, servicecontroller , html w/ thymeleaf, reference. doing wrong???
converters:
parttostring:
public class parttostringconverter implements converter<part, string> { /** string represents null. */ private static final string null_representation = "null"; @resource private partrepository partrepository; @override public string convert(final part part) { if (part.equals(null_representation)) { return null; } try { return part.getid().tostring(); } catch (numberformatexception e) { throw new runtimeexception("could not convert `" + part + "` valid id"); } } }
stringtopart:
public class stringtopartconverter implements converter<string, part> { /** string represents null. */ private static final string null_representation = "null"; @resource private partrepository partrepository; @override public part convert(final string idstring) { if (idstring.equals(null_representation)) { return null; } try { long id = long.parselong(idstring); return this.partrepository.findbyid(id); } catch (numberformatexception e) { throw new runtimeexception("could not convert `" + id + "` valid id"); } } }
relevant parts of webmvcconfig:
@configuration public class webmvcconfig extends webmvcconfigurationsupport { ... @bean(name="conversionservice") public conversionservice getconversionservice(){ conversionservicefactorybean bean = new conversionservicefactorybean(); bean.setconverters(getconverters()); bean.afterpropertiesset(); conversionservice object = bean.getobject(); return object; } private set<converter> getconverters() { set<converter> converters = new hashset<converter>(); converters.add(new parttostringconverter()); converters.add(new stringtopartconverter()); system.out.println("converters added"); return converters; } }
part repository looks this:
@repository @transactional(readonly = true) public class partrepository { protected static logger logger = logger.getlogger("repo"); @persistencecontext private entitymanager entitymanager; @transactional public part update(part part){ try { entitymanager.merge(part); return part; } catch (persistenceexception e) { return null; } } @suppresswarnings("unchecked") public list<part> getallparts(){ try { return entitymanager.createquery("from part").getresultlist(); } catch (exception e) { return new arraylist<part>(); } } public part findbyid(long id){ try { return entitymanager.find(part.class, id); } catch (exception e) { return new part(); } } }
edit part of servicecontroller:
@controller @requestmapping("/") public class serviscontroller { protected static logger logger = logger.getlogger("controller"); @autowired private servisrepository servisrepository; @autowired private servistyperepository servistyperepo; @autowired private partrepository partrepo; @autowired private vehiclerepository2 vehiclerepository; /*-- **************************************************************** -*/ /*-- editing servis methods -*/ /*-- -*/ /*-- **************************************************************** -*/ @requestmapping(value="/admin/servisi/editservis", method = requestmethod.get) public string geteditservis(@requestparam(value="id", required=true) long id, model model){ logger.debug("received request show edit page"); list<servistype> servistypelist = servistyperepo.getallst(); list<part> partlist = partrepo.getallparts(); list<part> selectedparts = new arraylist<part>(); servis s = servisrepository.getbyid(id); (part part : partlist) { (part parts : s.getparts()) { if(part.getid()==parts.getid()){ selectedparts.add(part); system.out.println(part); } } } s.setparts(selectedparts); logger.debug("radjeni dijelovi " + s.getparts().tostring()); logger.debug("radjeni dijelovi " + s.getparts().size()); s.setvehicle(vehiclerepository.findbyvin(s.getvehicle().getvin())); model.addattribute("partsatribute", partlist); model.addattribute("servistypesatribute", servistypelist); model.addattribute("servisattribute", s); return "/admin/servis/editservis"; } @requestmapping(value="/admin/servisi/editservis", method = requestmethod.post) public string saveeditservis(@modelattribute("servisattribute") @valid servis servis, bindingresult result){ logger.debug("received request save edit page"); if (result.haserrors()) { string ret = "/admin/servis/editservis"; return ret; } servisrepository.update(servis); return "redirect:/admin/servisi/listservis?id="+servis.getvehicle().getvin(); } }
view displays service correctly, not preselect parts.
editservice:
<!doctype html system "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring3-3.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head th:include="fragments/common :: headfragment"> <title>edit vehicle service</title> </head> <body> <div th:include="fragments/common :: adminheaderfragment"></div> <div class="container"> <section id="object"> <div class="page-header"> <h1>edit service</h1> </div> <div class="row"> <form action="#" th:object="${servisattribute}" th:action="@{/admin/servisi/editservis}" method="post" class="form-horizontal well"> <input type="hidden" th:field="*{vehicle.vin}" class="form-control input-xlarge" /> <div class="form-group" th:class="${#fields.haserrors('vehicle.vin')} ? 'form-group has-error' : 'form-group'"> <label for="vehicle.licenseplate" class="col-lg-2 control-label">license plate</label> <div class="col-lg-10"> <input type="text" th:field="*{vehicle.licenseplate}" class="form-control input-xlarge" placeholder="license plate" readonly="readonly"/> <p th:if="${#fields.haserrors('vehicle.licenseplate')}" class="label label-danger" th:errors="*{vehicle.licenseplate}">incorrect lp</p> </div> </div> <div class="form-group" th:class="${#fields.haserrors('servicedate')} ? 'form-group has-error' : 'form-group'"> <label for="servicedate" class="col-lg-2 control-label">servis date: </label> <div class="col-lg-10"> <input type="date" th:field="*{servicedate}" class="form-control input-xlarge" placeholder="servis date" /> <p th:if="${#fields.haserrors('servicedate')}" class="label label-danger" th:errors="*{servicedate}">incorrect date</p> </div> </div> <div class="form-group" th:class="${#fields.haserrors('servicetype.id')} ? 'form-group has-error' : 'form-group'"> <label for="servicetype.id" class="col-lg-2 control-label">vrsta servisa</label> <div class="col-lg-10"> <select th:field="*{servicetype.id}" class="form-control"> <option th:each="servistype : ${servistypesatribute}" th:value="${servistype.id}" th:selected="${servistype.id==servisattribute.servicetype.id}" th:text="${servistype.name}">vrsta servisa</option> </select> <p th:if="${#fields.haserrors('servicetype.id')}" class="label label-danger" th:errors="${servicetype.id}">incorrect vin</p> </div> </div> <div class="form-group" th:class="${#fields.haserrors('parts')} ? 'form-group has-error' : 'form-group'"> <label for="parts" class="col-lg-2 control-label">parts</label> <div class="col-lg-10"> <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 , serial no.</option> </select> <p th:if="${#fields.haserrors('parts')}" class="label label-danger" th:errors="*{parts}">incorrect part id</p> </div> </div> <div class="form-group" th:class="${#fields.haserrors('completed')} ? 'form-group has-error' : 'form-group'"> <label for="completed" class="col-lg-2 control-label">is service completed?</label> <div class="col-lg-10"> <select th:field="*{completed}" class="form-control"> <option value="true">yes</option> <option value="false">no</option> </select> <p th:if="${#fields.haserrors('completed')}" class="label label-danger" th:errors="*{completed}">incorrect checkbox</p> </div> </div> <hr/> <div class="form-actions"> <button type="submit" class="btn btn-primary">edit service</button> <a class="btn btn-default" th:href="@{/admin/servisi/listservis(id=${servisattribute.vehicle.vin})}">cancel</a> </div> </form> </div> </section> <div class="row right"> <a class="btn btn-primary btn-large" th:href="@{/admin/part/listpart}">back list</a> </div> <div th:include="fragments/common :: footerfragment"></div> </div> <!-- /.container --> <div th:include="fragments/common :: jsfragment"></div> </body> </html>
update: avnish, made several changes , came with:
adding conversion service did not work, after researching , reading docs, went , changed webmvcconfig file in stead of @bean added (all had @ documentation on webmvcconfigurationsupport:
@override protected void addformatters(formatterregistry registry){ registry.addformatter(new parttwowayconverter()); }
then removed converters , made 1 formatter magic. don't confused name, formater:
public class parttwowayconverter implements formatter<part>{ /** string represents null. */ private static final string null_representation = "null"; @resource private partrepository partrepository; public parttwowayconverter(){ super(); } public part parse(final string text, final locale locale) throws parseexception{ if (text.equals(null_representation)) { return null; } try { long id = long.parselong(text); // part part = partrepository.findbyid(id); // not work controller part part = new part(); // works part.setid(id); // return part; } catch (numberformatexception e) { throw new runtimeexception("could not convert `" + text + "` valid id"); } } public string print(final part part, final locale locale){ if (part.equals(null_representation)) { return null; } try { return part.getid().tostring(); } catch (numberformatexception e) { throw new runtimeexception("could not convert `" + part + "` valid id"); } } }
then edited html. not make thymeleaf work out, did this:
<div class="form-group" th:class="${#fields.haserrors('parts')} ? 'form-group has-error' : 'form-group'"> <label for="parts" class="col-lg-2 control-label">parts</label> <div class="col-lg-10"> <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 , serial no.</option> </select> <p th:if="${#fields.haserrors('parts')}" class="label label-danger" th:errors="*{parts}">incorrect part id</p> </div> </div>
and finally, after lot of trouble , conversion errors not figure out, changed controller update method:
@requestmapping(value="/admin/servisi/editservis", method = requestmethod.post) public string saveeditservis(@modelattribute("servisattribute") @valid servis servis, bindingresult result){ logger.debug("received request save edit page"); if (result.haserrors()) { logger.debug(result); string ret = "/admin/servis/editservis"; return ret; } list<part> list = new arraylist<part>(); (part part : servis.getparts()) { list.add(partrepo.findbyid(part.getid())); } servis updating = servisrepository.getbyid(servis.getid()); updating.setcompleted(servis.getcompleted()); updating.setparts(list); // if setting servis.getparts() not work updating.setservicedate(servis.getservicedate()); updating.setservicetype(servis.getservicetype()); servisrepository.update(updating); return "redirect:/admin/servisi/listservis?id="+servis.getvehicle().getvin(); }
even though works, still not happy, since code looks more patching proper coding. still puzzled why return part partrepository did not work. , why thymeleaf did not work... if can send me right direction, appreciate it!
thymeleaf compares values (for inclusion of selected="selected" tag in option html) using spring frameworks selectedvaluecomparator.isselected inherently depends upon java equality first. if fails, falls string representation of both values. following excerpt it's documentation
utility class testing whether candidate value matches data bound value. eagerly attempts prove comparison through number of avenues deal issues such instance inequality, logical (string-representation-based) equality , propertyeditor-based comparison.
full support provided comparing arrays, collections , maps.
equality contract
single-valued objects equality first tested using standard java equality. such, user code should endeavour implement object.equals speed comparison process. if object.equals returns false attempt made @ exhaustive comparison aim being prove equality rather disprove it.
next, attempt made compare string representations of both candidate , bound values. may result in true in number of cases due fact both values represented strings when shown user.
next, if candidate value string, attempt made compare bound value result of applying corresponding propertyeditor candidate. comparison may executed twice, once against direct string instances, , against string representations if first comparison results in false.
for specific case, i'd write down conversion service part object converted string described varietyformatter in http://www.thymeleaf.org/doc/html/thymeleaf-spring3.html#configuring-a-conversion-service . post i'd use th:value="${part}" , let selectedvaluecomparator it's magic of comparing objects , add selected="selected" part in html.
also in design, implement equals method based on primary key (usually @ top level abstract entity other entities inherit). further strengths natural comparison of domain objects in system throughout. doing similar in design?
hope helps!!
Comments
Post a Comment