Is it possible to build a table dynamically using Thymeleaf?
Essentially what i hope to achieve is ability to pass any object and the table would show the number of columns representing number of fields in object.
e.g.
Object 1
first name
last name
DOB
Object 2
number
code
street
city
when passed to this same thymleaf table it would generate different results:
Object 1 Table:
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>DOB</td>
</tr>
Object 2 Table:
<tr>
<td>Number</td>
<td>Code</td>
<td>Street</td>
<td>City</td>
</tr>
Concept and the background
This post will give you an idea of how to fetch the properties of a class and get the values of those properties using org.apache.commons.beanutils.PropertyUtils
https://stackoverflow.com/a/13960004/1251350
Implementation Suggestion
Build a bean with the method to use the above methodology to get a map<String, Object> of properties of a passed object.
#Service("objService")
class ObjectService {
public Map<String, Object> convertToArray(Object object){
// the logic to be taken from
// https://stackoverflow.com/a/13960004/1251350
}
}
Then in the thymeleaf template get the object passed as a fragment argument and iterate the map http://forum.thymeleaf.org/How-to-iterate-HashMap-td3621264.html
<div th:fragment="objDisplay(obj)">
<div th:each="entry : #objService.convertToArray(obj)">
<!-- Thymeleaf template to display Map -->
<!-- http://forum.thymeleaf.org/How-to-iterate-HashMap-td3621264.html -->
</div>
</div>
I didn't put the effort to write the code for you, as I believe you can do it youself on this guidance. Cheers!
Related
I want to create simple list in a form of table like on the image:
I am confused how to implement update/delete actions.
Both are [HttpPost] methods.
But I can't create forms inside <td> tags.
What is the valid way to make a table in the way that I want?
I think I have understood what do you need.
You can make one simple table and into each row put only one td which keep form.
Something like this:
<table>
#{
foreach (var item in Model.MyCollection)
{
<tr>
<td>
<form>
<table>
and hear put your existing row with your columns <tr><td>...</td><td>...</td></tr>
</table>
</form>
</td>
</tr>
}
}
and you will have one form for each record.
I am trying to use a display template (Pet.cshtml), which I have placed in ~/Views/Shared/DisplayTemplates, as per convention.
The Index action gets the IEnumerable and passes it to Index.cshtml, which passes it along to _PetTablePartial. So far, so good. However, when Html.DisplayForModel is called, I get this error:
The model item passed into the dictionary is of type 'Pet', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[Pet]'.
But I (think) I can clearly see that the model item is in fact an IEnumerable. What am I doing wrong?
Controller:
public ActionResult Index()
{
return View(pet.GetPets()); // returns IEnumerable<Pet>
}
Index.cshtml:
#model IEnumerable<Pet>
{Html.RenderPartial("_PetTablePartial", Model);}
...
_PetTablePartial.cshtml:
#model IEnumerable<Pet>
#Html.DisplayForModel()
~/Shared/DisplayTemplates/Pet.cshtml:
#model IEnumerable<Pet>
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
...
Pet.cshtml should have a model type of Pet, because you are only dealing with a single pet here.
DisplayTemplates automatically enumerate over a collection, and call your DisplayTemplate with a single item. That's one of their benefits. You don't need to do any enumeration.
Just change Pet.cshtml's type to Pet
I also suspect you don't want to have a separate table for each pet. So what you want is to create the table and header In your partial view, then only have a single data row in Pet.cshtml, because Pet.cshtml will be called multiple times, once for each row.
PetTablePartial.cshtml:
#model IEnumerable<Pet>
<table>
<tr>
<th> Pet Name </th>
</tr>
#Html.DisplayForModel()
</table>
~/Shared/DisplayTemplates/Pet.cshtml:
#model Pet
<tr>
<td>#Html.DisplayFor(x => x.Name)</td>
</tr>
In the Pet.cshtml you are passing in IEnumerable<Pet>, but then trying to access the Name property of the model. IEnumerable does not have a Name property.
In general, you would wrap this with a foreach loop so you can access the Name property of the elements on the list. However, since you are trying to write out the table header, you only want to write it out once and not traverse the list.
Take a look at these other SO questions:
#Html.DisplayNameFor for details model
How to get the column titles from the Display(Name=) DataAnnotation for a strongly typed list scaffold view at runtime?
I have a list of table names in a request attribute, "BillSummaryTables". I am iterating through the list and I want to use each table name to get a request attribute for that particular table name. Corresponding to each table name I have another list in request attribute and I want to iterate through that.
This is what I am doing.
<s:iterator value='#request.BillSummaryTables' var="tableName" status="itStatus">
<div class="contentbox" role="content">
<table class="rpt">
<s:iterator value="#request.get('%{#tableName}').getData()" var="ocRow" status="itStatus">
<tr style="border:1px solid #CCCCCC">
<s:iterator value='#ocRow' var="cell" status="itStatus2">
<td>
<s:property value="#cell.getValue()"/>
</td>
</s:iterator>
</tr>
</s:iterator>
<tr>
<s:iterator value="#request.get('%{#tableName}').getData()" var="ocTotal">
<td>
<s:property value="#ocTotal"/>
</td>
</s:iterator>
</tr>
</table>
</div>
</s:iterator>
I have also tried
#request[<s:property value="#tableName" />].getData()
and
#request['<s:property value="#tableName" />'].getData()
and
#request.%{#tableName}
But nothing is returned in any case.
However, this code works fine if I hard code the values.
i.e. if i use: #request['other_charges'].getData()
Note: I am able to retrieve the list of tableName (#request.BillSummaryTables).
#1) You are using three nested iterators, but both the first and the second have an instance of IteratorStatus called itStatus; they must have different names to work.
#2) If the Lists corresponding to the Table name is, effectively, a List, then you should iterate the list, not the getData() stuff (what is that ?)
#3) Why using request ? why not simply using an HashMap on the Action (with the getter), adding elements dynamically using table names as key ?
#4) This #request[<s:property value="#tableName" />].getData() will obviously not work if put inside another Struts2 tag, like an Iterator (cannot nest Struts2 tags).
However, try something like this (I stripped the second iterator, make it running before, then add stuff), and see if it works (and what it prints):
<s:iterator value='#request.BillSummaryTables' var="tableName" status="statusAllTables">
<div class="contentbox" role="content">
<br/>==== START DEBUG ====
<br/>Current table name: [<s:property value="#tableName"/>]
<br/>Corresponding request object: [<s:property value="#request['%{#tableName}']"/>]
<br/>getData on that object: [<s:property value="#request['%{#tableName}'].getData()"/>]
<br/>===== END DEBUG =====
<table class="rpt">
<s:iterator value="#request['%{#tableName}'].getData()" var="ocRow" status="statusThisTable">
<tr style="border:1px solid #CCCCCC">
<s:iterator value='#ocRow' var="cell" status="statusThisField">
<td>
<s:property value="#cell.getValue()"/>
</td>
</s:iterator>
</tr>
</s:iterator>
</table>
</div>
</s:iterator>
EDIT
Ok, but then why are you using request.setAttribute ? Actions are created per-request... just use a private List<MyObjects> myObjects with its getter (public List<MyObject> getMyObjects()), and call it from JSP with <s:iterator value="myObjects"> (in your case, <s:iterator value="myObjects.data">.
Please note that .getData() in OGNL should become .data (i didn't noticed it before), removing the get, lowering the first letter of the method, and removing round brackets...
Retry and let us know.
I am trying to load a value into a field on a view from the action on a controller. How do I reference the specific field I want to load? In .NET it would be something like: in the button click event, this.txtName.Text = "John". I don't understand how to do that from a controller and specifically how to reference the view field. I have tried using the params object but it is coming null. I know I am getting to the action based on println statements that I have used.
Here are the relevant code snippets:
from the view:
<td valign="top" class="value ${hasErrors(bean: planetInstance, field: 'name', 'errors')}">
<g:textField name="name" value="${planetInstance?.name}"/>
</td>
<td class="load">
<g:actionSubmit value="Load" action="nameLoad"/>
</td>
from the controller:
def nameLoad = {
// I want to reference and load the "name" textField from the view
}
Any help would be appreciated.
I cannot tell from your code, but you may have forgotten to map the variable name in your controller action nameLoad:
class YourController{
def nameLoad = {
def name = Planet.get(params.id).name //This can be whatever you need it to be to get the correct value assigned to the "name" variable. Here I assume you have a domain class called "Planet" which may or may not be the case.
return [name:name] //This is where you map key/value pairs. The name of the key is what you will type to access the value in your view. The value is the name of the variable you are dealing with, in this case "name".
}
}
With the way the code above is set up Grails will assume there is a view called nameLoad in the folder YourController. So the URL will be something like:
http://localhost:8080/yourapp/yourcontroller/nameload
In that view you will access name like this:
${name}
Which could be used in any number of tags, like the <g:select> tag for instance:
<g:select from="${name}" />
You should be able to access the value of the text field with params.name. If that's not working, you may have a problem with your view. Are the g:textField and g:actionSubmit tags enclosed in a form or g:form?
I have a search page that display a search result. The search result is a list of persons that matched the specific search. I'm iterating through this list displaying them in a table. As headers for this table I want the DisplayName from the model. If I don't inherit IEnumerable I wouldn't be able to iterate through the list. I'm new at this MVC thing =)
I iterate through the result like this:
<% foreach (var item in Person) { %>
<%: item.surname %>
<% } %>
But how do I print the "DisplayName" of an attribute without iterating through the whole list? I would just like to do:
<%: Html.LabelFor(m => m.surname) %>
If it's any help I inherit an IEnumerable at the top of the page:
<%# Page Inherits="System.Web.Mvc.ViewPage<IEnumerable<RegistryTest.Models.Person>>" %>
Edit
I want to display "Your surname" but I don't know how to access it from the view.
[DisplayName("Your surname")]
public object surname { get; set; }
Here's a very similar question that hasn't been answered either: Can I use LabelFor() when the page inherits from IEnumerable<T>?
If you only need to display specifics of one person; you should consider sending only one person to the view instead of a complete list of persons. In that case
Model.Surname
would work just like that. So instead of:
Inherits="System.Web.Mvc.ViewPage<IEnumerable<RegistryTest.Models.Person>>"
do
Inherits="System.Web.Mvc.ViewPage<RegistryTest.Models.Person>"
In this case a single person is loaded into your Model and Model.Property works fine. If you want an IENumerable<>, think about what that means. You are sending a list of persons, so the only thing in your "Model" is a IENumerable<> of persons. There is no way that the view can know what you want if you call Model.Property, since in the Model there are multiple Objects and the view doesn't know which Object you want to get the Property from.
Bottom line, if you want to call Model.Property (Model.surname) but also want to send an IENumerable you are having a design flaw. If you send a list you should want to do something with the complete list (iterate through and do something with the contents). If you just want to do something with one person in that list, re-design your view/controller and send that single person; then you can use Model.Property.
//EDIT BASED UPON COMMENTS
As I see it now you either want to do one of those two things (I do not know which):
Show the records of an item in your list in a table and put the DisplayName of the current object shown in the table in the header.
Show all items of the list in your table and put some sort of DisplayName in the header. This makes less sence but it could be that you mean to name your list.
Situation 1
This is working as the rest of your code? The following would work just fine.
<% foreach (var item in Model) { %>
<table>
<th>item.DisplayName</th>
<tr>
<td>item.Property1</td>
<td>item.Property2</td>
</tr>
</table>
<% } %>
Situation 2
If you want a DisplayName of the list (??) you should create a ViewModel containing the IENumerable of Persons and a public string ListName. Now you can do something like:
<table>
<th>Model.ListName</th>
<% foreach (var item in Model) { %>
<tr>
<td>item.Property1</td>
<td>item.Property2</td>
</tr>
<% } %>
</table>
this would create a table with the name of your List (given in the ViewModel) as header and as items in the table you have your persons.
Design problem?
However, I would love to see you write some more information in your question above. Give us some more information on what you want to do. Do you want to show records of each Person in the list one-by-one? In that case I would recommend you create a Partial View where you put your table. Then you would get something like:
<% foreach (var item in Model) { %>
<% Html.RenderPartial("TablePerson",item); %>
<% } %>
tableperson.ascx:
...
Inherits="System.Web.Mvc.ViewPage<IEnumerable<RegistryTest.Models.Person>>"
...
<table>
<th>Model.DisplayName</th>
<tr>
<td>Model.Property1</td>
<td>Model.Property2</td>
</tr>
</table>
So, we need more information I'm afraid :)
If it's a collection with every entry having the same surname then try model[0].surname or model.ToList().First().surname
You don't need ToList() if its a List<T> already. Then it would be just model.First()
You are specifying that the page model is IEnumerable, and you say you would like to print a property of an element. Since you have a list of elements, you need to specify which of the elements you would like to retrieve the property from.
I you want a specific index in the list you will need to convert the IEnumerable collection to IList (ToList()), depending on the criteria, you may also be able to find the required element using something like a LINQ Single() operation.
Otherwise you could select the property from all the element in the list using Model.Select(m => m.PropertyName) which will give you a list of just this property, and then concatenate this list to a single string.