Spring, Thymleaf and lists of strings - spring

Okay, I a wet-behind-the-ears newcomer to Spring and Thymleaf. I'm trying to do something so simple it should be a no-brainer. But I can't get it to work. The simple question is - how do you show a list of strings in an web page?
I have the following model
import java.util.List;
public class TestModel {
private List<String> list = null;
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public List<String> getList() { return list; }
public void setList(final List<String> list) {
this.list = list;
}
}
My web page contains the following:
<div th:if="${greeting.list != null}">
<h1>Result</h1>
<ul>
<th:block th:object="${greeting}" th:each="item : ${list}">
<li th:text="${item.name}">Item description here...</li>
</th:block>
</ul>
</div>
I added the ".name" to "item" only because I found a couple of examples where they had a list of strings and did something similar. But they had the ".name" on the object.
But it still doesn't work. The unordered list ends up empty. I.e. There isn't any list items inside the unordered tags.
What am I doing wrong? Pointers gladly accepted.

Since there's no example of filling model I supposed you put some strings into instance of TestModel's list field like this.
TestModel greeting= new TestModel();
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
model.addAttribute("greeting", greeting);
Then there are more errors in your Thymeleaf template example.
If you are using object selection through th:object you must at first place use asterix * to access object properties. Asterisk syntax evaluates expressions on selected objects instead of context variables map.
Object selection affects only children nodes in DOM.
In your example you want iterate over list of strings (List<String>) but you want to access property name which in fact don't exists on Java String object.
You must fix your Thymeleaf template in one way - see examples.
No object selection at all
<div th:if="${greeting.list != null}">
<h1>Result</h1>
<ul>
<li th:each="item : ${greeting.list}" th:text="${item}">Item description here...</li>
</ul>
</div>
Proper object selection
<div th:if="${greeting.list != null}">
<h1>Result</h1>
<ul>
<th:block th:object="${greeting}">
<li th:each="item : *{list}" th:text="${item}">Item description here...</li>
</th:block>
</ul>
</div>

<table th:object="${userList}" id="userTable" border="1">
<tr th:each="user :${userList}">
<td th:text="${user.getName()}"></td>
<td th:text="${user.getEmail()}"></td>
</tr>
</table>
Another example using table and Object. Might be useful to someone else.

In thymeleaf, if you are looking to create in a template a list of strings and compare it with a string, you can use the following structure.
th:if="${#lists.contains({'RO', 'UK', 'DE', 'BE'}, #session.getAttribute('country'))}"

Related

How to show appropriate message on page with Thymeleaf if list is empty

I have some controller that is connected with some lists, now that list can be empty, I'm trying to provide a user message on page if list is empty. To be more clear, user can have wallet, inside that wallet user can create a transactions, because all of that I have a page Transactions now, if user still didnt create any transaction but visit that page, I want to show him message like You didnt create any transaction.
I found this example, and tried to apply on my problem, but so far it didnt work: How to check null and empty condition using Thymeleaf in one single operation?
This is my controller:
#GetMapping("/userTransactions/{user_id}")
public String getUserTransactions(#PathVariable("user_id") long user_id, TransactionGroup transactionGroup, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
long userId = user.getId();
model.addAttribute("userId", userId);
List<Transaction> transactions = transactionRepository.getTransactionsByUserId(user_id);
List<TransactionGroup> transactionByDate = new ArrayList<>();
List<Transaction> transOnSingleDate = new ArrayList<>();
boolean currDates = transactions.stream().findFirst().isPresent();
if (currDates) {
LocalDate currDate = transactions.get(0).getDate();
TransactionGroup transGroup = new TransactionGroup();
for (Transaction t : transactions) {
if (!currDate.isEqual(t.getDate())) {
transGroup.setDate(currDate);
transGroup.setTransactions(transOnSingleDate);
transactionByDate.add(transGroup);
transGroup = new TransactionGroup();
transOnSingleDate = new ArrayList<>();
}
transOnSingleDate.add(t);
currDate = t.getDate();
}
transGroup.setDate(currDate);
transGroup.setTransactions(transOnSingleDate);
transactionByDate.add(transGroup);
} else {
System.out.println("Empty");
model.addAttribute("transactionGroup", "NoData");
}
model.addAttribute("transactionGroup", transactionByDate);
return "transactions";
}
And that seems to work fine, I mean, if I didn't create a transaction, message System.out.println("Empty"); will be printed, but message on page is not displayed neither.
This is thymeleaf:
<div class="date" th:each="singleGroup : ${transactionGroup}">
<h1 th:text="${singleGroup .date}"></h1>
<div class="transaction" th:each="singleTrans : ${singleGroup.transactions}">
<h2>Amount: <span th:text="${singleTrans.amount}"></span></h2><br>
<h2>Note: <span th:text="${singleTrans.note}"></span></h2><br>
<h2>Wallet name: <span th:text="${singleTrans .walletName}"></span></h2><br>
<h2>Expense Category: <span th:text="${singleTrans .expenseCategories}"></span></h2><br>
<h2>IncomeCategory: <span th:text="${singleTrans .incomeCategories}"></span></h2>
<a class="card__link" th:href="#{/api/transaction/delete/{id}(id=${singleTrans.id})}"
th:data-confirm-delete="|Are you sure you want to delete ${singleTrans.walletName} wallet?|"
onclick="if (!confirm(this.getAttribute('data-confirm-delete'))) return false">Delete</a>
<hr>
</div>
<th:block th:if="${transactionGroup=='NoData'}"> No Data Found </th:block>
</div>
And here is the part: <th:block th:if="${transactionGroup=='NoData'}"> No Data Found </th:block> But its not displayed if list is empty
What I'm missing?
You can check if your list is empty like this
<div th:if="${#lists.isEmpty(transactionGroup)}"> No Data Found </div>
<div th:if="${#lists.size(transactionGroup) ==0}"> No Data Found </div>
In addition to that, your th:block is placed inside the list outer loop and it should be outside. That's why you're not seeing anything when the list is empty.

How to display existing and newly created values in EL in Spring MVC?

Only the last value of the model is displayed as EL, so the existing value is not displayed.
The BoardController code for that.
#RequestMapping("viewPage.do")
public String viewPage(HttpSession session,HttpServletRequest request, HttpServletResponse response,Model model,
#RequestParam(value="board_idx", defaultValue="0") int board_idx) throws IOException,IllegalStateException
{
// View comment list for this post
List<HashMap<String, Object>> commentList= bService.getBoardForComment(boardData.getBoard_idx());
// loop (comment.size)
for(int i = 0; i < commentList.size(); i++)
{
System.out.println("commentList Size : " + commentList.size());
//Saving the board_comm_idx value in the commentList to a variable
int board_comm_idx= (Integer) commentList.get(i).get("board_comm_idx");
//System.out.println("board_comm_idx : " + board_comm_idx);
// View comments in comments (viewed by board_idx, board_comm_idx)
List<HashMap<String, Object>> getCommentOfCommentList =
bService.getBoardCommentOfComment(board_idx, board_comm_idx);
model.addAttribute("cocList", getCommentOfCommentList);
System.out.println("GetCommentOfCommentList : " + getCommentOfCommentList);
System.out.println("cocList (Model) : " + model);
}
model.addAttribute("commentList", commentList); // CommentList Information
return "viewPage";
}
Here is the JSP code.
<c:forEach items="${cocList}" var="cocList">
<div class="commentList" style="margin-left:70px">
<div class="what">
<div class="nickname">
<span>
${cocList.board_c_of_c_writer}
</span>
</div>
<div class="writedatezone" style="margin-left: 715px;">
<span class="era">
<span class="glyphicon glyphicon-time enterReReply"></span>
<span class="commentWriteDate">
<fmt:formatDate value="${cocList.board_c_of_c_writeDate}" pattern="yyy-MM-dd"/>
</span>
</span>
</div>
<c:if test="${cocList.idx == idx}">
<div class="duzone">
<span class="deleteOrUpdateComment">
<a class="updateComment">수정</a>
<a class="deleteComment" onclick="deleteCoc(${cocList.board_idx},${cocList.board_c_of_c_idx})">삭제</a>
<input type="hidden" value="${cocList.board_comm_idx}">
<input type="hidden" value="${cocList.board_c_of_c}">
<input type="hidden" name="board_idx" value="${boardInformation.board_idx}">
</span>
</div>
</c:if>
</div>
<pre>${cocList.board_c_of_c_content}</pre>
</div>
</c:forEach>
First, it is the log of when you first commented on the comment.
GetCommentOfCommentList : [{board_idx=3, board_c_of_c_writer=웹개발자, board_c_of_c_content=Test, board_c_of_c_idx=8, board_comm_idx=5, idx=4, board_c_of_c_writeDate=2017-11-30 10:33:06.0}]
cocList (Model) : {cocList=[{board_idx=3, board_c_of_c_writer=웹개발자, board_c_of_c_content=Test, board_c_of_c_idx=8, board_comm_idx=5, idx=4, board_c_of_c_writeDate=2017-11-30 10:33:06.0}]}
After that, if you make a new comment,
GetCommentOfCommentList : [{board_idx=3, board_c_of_c_writer=웹개발자, board_c_of_c_content=Test, board_c_of_c_idx=8, board_comm_idx=5, idx=4, board_c_of_c_writeDate=2017-11-30 10:33:06.0}]
cocList (Model) : {cocList=[{board_idx=3, board_c_of_c_writer=웹개발자, board_c_of_c_content=Test, board_c_of_c_idx=8, board_comm_idx=5, idx=4, board_c_of_c_writeDate=2017-11-30 10:33:06.0}]}
GetCommentOfCommentList : []
cocList (Model) : {cocList=[]}
As above, when you create a new comment, the comment list will disappear from the existing comment.
The last cocList is an empty string because you have not commented on the comment.
I am convinced that EL is wrong. How do I fix the EL to solve my problem?
Even though you have the
<c:forEach items="${cocList}" var="cocList">
cocList is a reference your list (model attribute), but since you are assigning it to something else var="cocList" you are going to have unintended consequences, you should name your list item to something else.
<c:forEach items="${cocList}" var="item">
You should then be able to reference the item like ${item.blah}
Think of
<c:forEach items="${cocList}" var="cocList">
as
for (Object cocList : cocList) {
...
}
Which obviously is bad, instead you want:
<c:forEach items="${cocList}" var="item">
which is
for (Object item : cocList) {
...
}
I believe that should fix your problem.
For what it is worth as well, most likely, you are not using EL or SpEL, you are using JSTL at the moment. Normally the SpEL tags are going to look like spring:..., c:... normally references JSTL.
I solved this problem.
I added getCommentofCommentList to commentList instead of modifying the query statement.
As follows.
for(int i = 0; i < commentList.size(); i++)
{
System.out.println("commentList Size : " + commentList.size());
// commentList 에 담긴 board_comm_idx 값을 변수에 저장
int board_comm_idx= (Integer) commentList.get(i).get("board_comm_idx");
//System.out.println("board_comm_idx : " + board_comm_idx);
// 댓글에 댓글 조회 (board_idx , board_comm_idx으로 조회)
List<HashMap<String, Object>> getCommentOfCommentList =
bService.getBoardCommentOfComment(board_idx, board_comm_idx);
commentList.get(i).put("cocList", getCommentOfCommentList);
}
I added the following code.
commentList.get(i).put("cocList", getCommentOfCommentList)
as the result, It's very well work.

How to pass checkbox values to the controller in Spring MVC

I have a jsp page with list of functions. Here in controller I get this list from database and pass it to jsp.
#RequestMapping(value = "/functionlist", method = RequestMethod.GET)
public ModelAndView functionList(Model model) throws Exception {
ModelAndView mv = new ModelAndView("functionList");
mv.addObject("functionList", getFunctionsFromDB());
return mv;
}
In my jsp page I create table using this list of functions
<table id="table">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<c:choose>
<c:when test="${not empty functionList}">
<c:forEach var="function" items="${functionList}">
<tr>
<td><input name="id" value="${function.id}" hidden></td>
<td>${function.name}</td>
<td>
<input type="checkbox" id="${function.id}" value="${function.action}"></td>
</tr>
</c:forEach>
</c:when>
</c:choose>
</tbody>
</table>
<button type="submit" name="save">Save</button>
I also give function id to checkbox id.
My Function entity is the following
public class Function {
private Integer id;
private String name;
private Boolean action;
...
}
I want to press button Save and get in controller "/functionlist/save" my list of checkbox values.
Try to add form like this to your jsp page
<form:form id="yourForm" action="/functionlist/save" method="POST" modelAttribute="functionList">
<c:forEach items="${functionList}" varStatus="status" var="function">
<tr>
<td>${function.name}</td>
<td>
<form:checkbox path="functionList[${status.index}].action"/>
</td>
</tr>
</c:forEach>
<input type="submit" value="submit" />
</form:form>
and in Controller you should have a method like this
#RequestMapping(value = { "/functionlist/save" }, method = RequestMethod.POST)
public String savePerson(#ModelAttribute("functionList")List<Function> functionList) {
// process your list
}
If this does not work, you can try to wrap you list.
public class FunctionListWrapper {
private List<Function> functionList;
public FunctionListWrapper() {
this.functionList = new ArrayList<Function>();
}
public List<Function> getFunctionList() {
return functionList;
}
public void setFunctionList(List<Function> functionList) {
this.functionList = functionList;
}
public void add(Function function) {
this.functionList.add(function);
}
}
in controller instead of passing list, pass wrapper
FunctionListWrapper functionListWrapper=new FunctionListWrapper();
functionListWrapper.setFunctionList(userService.getFunctionList());
mv.addObject("functionListWrapper", functionListWrapper);
For more details please take a look at this questions: question 1 and question 2
First you need to add name attribute to your checkbox inputs.You can get these in
an array on controller with same name as your name attribute.eg-
#RequestMapping(value = "/functionlist", method = RequestMethod.GET)
public ModelAndView functionList(Model model,#RequestParam("checkboxname")String[] checkboxvalues) throws Exception {
ModelAndView mv = new ModelAndView("functionList");
mv.addObject("functionList", getFunctionsFromDB());
return mv;
}
few things.
First you should think about a form object, which is the exchange entity between the view and the controller. The form entity will be something similar to the followng:
public class Form {
private List<Function> functions;
//getters & setters
}
secondly, since you are using Spring MVC, you should leverage the <%# taglib uri="http://www.springframework.org/tags/form" prefix="form" %> library. Consequently, your form will be:
<form:form commandName="form" action="/your/url">
<c:forEach items="${form.functions }" var="function" varStatus="status">
<tr>
<td><form:hidden path="functions[${status.index }].id"></td>
<td>${function.name}</td>
<td>
<form:checkbox path="functions[${status.index }].action" id="${function.id}" value="${function.action}"/>
</td>
</tr>
</c:forEach>
</form:form>
and finally the controller will be something like
#RequestMapping(value="/your/url", method=RequestMethod.POST)
public String postForm(#ModelAttribute("form") FormForm form)
{
//cool stuff here
}
please note that the proper HTTP method is POST, not GET. Yup, I know things work also with GET it's definitely a deprecated strategy.
In addition, figure out how I referred the list of Function in the Form. I used the variable function in the foreach statement when I had to display data, I referred the list when I had to bind the Form object for transferring data to the controller. Last but not least, I haven't tried the code. I wrote it on the fly just to give you hints about how to do it properly.
One last thing. Since you pass the Form to the JSP, you don't need anymore to fill the Model with the attribute you used. Just fill the form with your data and use it to diaplay them in the view. Just like this ${form.stuff.you.want}

Spring MVC 3.2 Thymeleaf Ajax Fragments

I'm building application with Spring MVC 3.2 and Thymeleaf templating engine. I'm a beginner in Thymeleaf.
I have everything working, including Thymeleaf but I was wondering if anyone knows of a simple and clear toturial on how to do simple Ajax request to controller and in result rendering only a part of a template (fragment).
My app has everything configured (Spring 3.2, spring-security, thymeleaf, ...) and works as expected. Now I would like to do Ajax request (pretty simple with jQuery but I don't wan't to use is since Thymeleaf in its tutorial, chapter 11: Rendering Template Fragments (link) mentiones it can be done with fragments.
Currently I have in my Controller
#RequestMapping("/dimensionMenuList")
public String showDimensionMenuList(Model model) {
Collection<ArticleDimensionVO> articleDimensions;
try {
articleDimensions = articleService.getArticleDimension(ArticleTypeVO.ARTICLE_TYPE);
} catch (DataAccessException e) {
// TODO: return ERROR
throw new RuntimeException();
}
model.addAttribute("dimensions", articleDimensions);
return "/admin/index :: dimensionMenuList";
}
the part of the view where I would like to replace <ul></ul> menu items:
<ul th:fragment="dimensionMenuList" class="dropdown-menu">
<li th:unless="${#lists.isEmpty(dimensions)}" th:each="dimension : ${dimensions}">
</li>
</ul>
Any clue is greatly appreciated. Especially if I don't have to include any more frameworks. It's already too much for java web app as it is.
Here is an approach I came across in a blog post:
I didn't want to use those frameworks so in this section I'm using jQuery to send an AJAX request to the server, wait for the response and partially update the view (fragment rendering).
The Form
<form>
<span class="subtitle">Guest list form</span>
<div class="listBlock">
<div class="search-block">
<input type="text" id="searchSurname" name="searchSurname"/>
<br />
<label for="searchSurname" th:text="#{search.label}">Search label:</label>
<button id="searchButton" name="searchButton" onclick="retrieveGuests()" type="button"
th:text="#{search.button}">Search button</button>
</div>
<!-- Results block -->
<div id="resultsBlock">
</div>
</div>
</form>
This form contains an input text with a search string (searchSurname) that will be sent to the server. There's also a region (resultsBlock div) which will be updated with the response received from the server.
When the user clicks the button, the retrieveGuests() function will be invoked.
function retrieveGuests() {
var url = '/th-spring-integration/spring/guests';
if ($('#searchSurname').val() != '') {
url = url + '/' + $('#searchSurname').val();
}
$("#resultsBlock").load(url);
}
The jQuery load function makes a request to the server at the specified url and places the returned HTML into the specified element (resultsBlock div).
If the user enters a search string, it will search for all guests with the specified surname. Otherwise, it will return the complete guest list. These two requests will reach the following controller request mappings:
#RequestMapping(value = "/guests/{surname}", method = RequestMethod.GET)
public String showGuestList(Model model, #PathVariable("surname") String surname) {
model.addAttribute("guests", hotelService.getGuestsList(surname));
return "results :: resultsList";
}
#RequestMapping(value = "/guests", method = RequestMethod.GET)
public String showGuestList(Model model) {
model.addAttribute("guests", hotelService.getGuestsList());
return "results :: resultsList";
}
Since Spring is integrated with Thymeleaf, it will now be able to return fragments of HTML. In the above example, the return string "results :: resultsList" is referring to a fragment named resultsList which is located in the results page. Let's take a look at this results page:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
</head>
<body>
<div th:fragment="resultsList" th:unless="${#lists.isEmpty(guests)}" id="results-block">
<table>
<thead>
<tr>
<th th:text="#{results.guest.id}">Id</th>
<th th:text="#{results.guest.surname}">Surname</th>
<th th:text="#{results.guest.name}">Name</th>
<th th:text="#{results.guest.country}">Country</th>
</tr>
</thead>
<tbody>
<tr th:each="guest : ${guests}">
<td th:text="${guest.id}">id</td>
<td th:text="${guest.surname}">surname</td>
<td th:text="${guest.name}">name</td>
<td th:text="${guest.country}">country</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
The fragment, which is a table with registered guests, will be inserted in the results block.
Rendering only Thymeleaf fragments also works well with ModelAndView.
Your controller
#RequestMapping(value = "/feeds", method = RequestMethod.GET)
public ModelAndView getFeeds() {
LOGGER.debug("Feeds method called..");
return new ModelAndView("feeds :: resultsList");
}
Your view
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head></head>
<body>
<div th:fragment="resultsList" id="results-block">
<div>A test fragment</div>
<div>A test fragment</div>
</div>
</body>
</html>
What's actually rendered
<div id="results-block">
<div>A test fragment</div>
<div>A test fragment
</div>
</div>
As an alternate version to Sohail's great answer, I want to give a version that using javascript can send the whole th:object to the controller, integrating Thymeleaf in your forms, not having to use #PathVariable which becomes messy or not usable at all when you've forms with many fields.
For the form (using an example which returns an object which has an id and a name Strings, and feeds a combobox with a Map that has some of those objects as values) we have:
<form method="post" th:action="#{/yourMapping}" th:object="${object}" id="yourFormId">
<select th:field="*{mapOfObjects}">
<option
th:each="entry: ${mapOfObjects}"
th:value="${entry.value.id}"
th:text="${entry.value.name}" >
</option>
</select>
<p>Name:
<input type="text" th:field="*{name}" />
</p>
</form>
When this form is submited (using a button with type submit for example) the whole document will be replaced. However we can intercept this submit with javascript and do it the ajax-way. To achieve this, we will add an interceptor to our form using a function. First call the function that adds the interceptor right after the form:
<script>formInterceptor("yourFormId");</script>
And the function looks like this (place it in the head of the document or wherever suits your needs):
<script>
function formInterceptor(formName) {
var $form = $("#" + formName);
$form.on('submit', function(e) {
e.preventDefault();
$.ajax({
url : $form.attr('action'),
type : 'post',
data : $form.serialize(),
success : function(response) {
if ($(response).find('.has-error').length) {
$form.replaceWith(response);
}
else{
$("#ajaxLoadedContent").replaceWith(response);
}
}
});
});
};
</script>
Now whenever the form is submited, this function will trigger, and it will:
Prevent the original form submit
Make an ajax call using the url defined in the form's th:action
Serialize the form data. Your controller will be able to recieve this in an object
Replace the part of your html code with the returned fragment
The replaced part should look like this
<div id="ajaxLoadedContent"></div>
And the controller can recieve the th:object in the form, with it's values filled, like this (Replace Object with your object's type and "object" with a proper name):
#PostMapping(value = /yourMapping)
public String ajaxCtrlExample(#ModelAttribute("object") Object object, Model model) {
return yourFragmentPath;
}
And that's everything. Call the function that adds the interceptor after every form you need in ajax-version.

Nested edit templates in mvc razor

I have seen many versions of this question but the answers always turn into "you don't need to do that" and never an answer.
I have a list of attributes about a product that I want to show in an unordered list with checkboxes to select particular attributes.
In My Model:
public List<ProductAttribute> ProductAttributes {get;set;}
in my Create.cshtml:
<div Class="ProductAttributes">
#Html.EditorFor(m => m.ProductAttributes, "ProductAttributeSelectorList")
</div>
In my ProductAttributeSelectorList.cshtml:
#model List<Models.DisplayLocationAttribute>
<div class="AttributeSelector">
<ul>
#foreach (var item in Model)
{
<li>
#Html.EditorFor(_ => item, "EditLocationAttributeList")
</li>
}
</ul>
</div>
And finally, in my EditLocationAttributeList.cshtml
#model Models.DisplayLocationAttribute
#Html.HiddenFor(m => m.Id)
#Html.CheckBoxFor(m => m.IsSelected)
<a href="#" alt="#Model.Description" >#Model.Name</a>
This all displays on the page perfectly I can style it like I want with CSS, but when the submit returns, my model.ProductAttributes collection is null.
I know I can bind directly to the EditLocationAttributeList and it will display and return a populated model.ProductAttributes if I use this:
#Html.EditorFor(m => m.ProductAttributes, "EditLocationAttributeList")
but now I do not have the unordered list that I would like to have. I could treat the template like an Item Template and have the line item tags embeded in that template but that seems smelly to have a template that is tightly coupled to another template.
Any Ideas?
Thanks in advance,
Tal
model.ProductAttributes is null, because the DefaultModelBinder is not able to reference each DisplayLocationAttribute back to the ProductAttribute property of your model. The simplest solution is to name your list elements as an array, so that for example each IsSelected element is named in the style ProductAttributes[n].IsSelected.
Add the following to ProductAttributeSelectorList.cshtml:
#model List<Models.DisplayLocationAttribute>
#{
var i = 0;
}
<div class="AttributeSelector">
<ul>
#foreach (var item in Model)
{
this.ViewData.TemplateInfo.HtmlFieldPrefix = "ProductAttributes[" +
i.ToString() + "]";
i++;
<li>
#Html.EditorFor(_ => item, "EditLocationAttributeList")
</li>
}
</ul>
</div>
#{
this.ViewData.TemplateInfo.HtmlFieldPrefix = "";
}
This will give you an indexed array, which the DefaultModelBinder will be able to associate to ProductAttributes. However, it builds a hard dependency to the name ProductAttributes. You can get around the hard dependency by several methods, such as passing the property name in the ViewBag.

Resources