I'm trying to pass an array from my controller to a QWeb template but can't achive to iterate through it...
--- Controller ---
return_projects = {}
for active_project in active_projects:
hours = 0
for analytic_line in active_project.line_ids:
hours += analytic_line.unit_amount
return_projects[active_project.id] = {
"name": active_project.display_name,
"hours": hours
}
return http.request.render('mymodule_briefing.crm_page', {
'projects': return_projects,
})
--- Template ---
<t t-foreach="projects" t-as="project">
<-- show project.name -->
<-- show project.hours-->
</t>
Inside the loop, project is the project id so you need to use it to get project data (name and hours) from projects
Example:
<t t-foreach="projects" t-as="project_id">
<div>
<t t-esc="projects[project_id]['name']"/>
<t t-esc="projects[project_id]['hours']"/>
</div>
</t>
You can render the template using the project records:
return odoo.http.request.render('mymodule_briefing.crm_page', {
'projects': active_projects,
})
And use the following code to loop over records and compute hours:
<t t-foreach="projects" t-as="project">
<div>
<t t-esc="project.name"/>
<t t-esc="sum(line.unit_amount for line in project.line_ids)"/>
</div>
</t>
Related
I tried to paginate my users CRUD, but it doesn't work. I can open users.html, it only shows 3 records, which is fine and if I use localhost:9001/users/page/1, 2 etc manually, it works fine. But the paginator under the table is an error screen.
My UserController:
#GetMapping("users/page/{pageNo}")
public String findPaginated(#PathVariable (value="pageNo") int pageNo, Model model) {
int pageSize = 3; //only 3 so I can easily test it
Page<User> page = userService.findPaginated(pageNo, pageSize);
List<User> listUsers = page.getContent();
model.addAttribute("currentPage", pageNo);
model.addAttribute("totalPages", page.getTotalPages());
model.addAttribute("totalItems", page.getTotalElements());
model.addAttribute("listUsers", listUsers);
return "/users";
}
**and**
#GetMapping("/users")
public String viewHomePage (Model model) {
//model.addAttribute("listUsers", userService.getAllUsers());
return findPaginated(1, model);
}
My UserService:
Page<User> findPaginated(int pageNo, int pageSize);
UserServiceImpl:
#Override
public Page<User> findPaginated(int PageNo, int PageSize) {
Pageable pageable = PageRequest.of(PageNo -1, PageSize);
return this.userRepository.findAll(pageable);
}
UserResository extends JpaRepository:
public interface UserRepository extends JpaRepository<User, Long> {
Pagintation in users.html:
<div th:if="${totalPages > 1}">
<div class="row col-sm-10">
<div class="col-sm-2">
<!--Total Rows: [[{$totalItems}]]
</div>
<div class="col-sm-1">
<span th:each="i: ${#numbers.sequence(1, totalPages)}">
valami
<a th:if="${currentPage != i}" th:href="#{'/page/' + ${i}}">[[${i}]]</a>
<span th:unless="${currentPage != i">[[${i}]]</span>
</span>
</div>
<!-- <div class="col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="#{'/page/' + ${currentPage + 1}}">Következő</a>
<span th:unless="${currentPage < totalPages}">Következő</span>
</div>
<div class="col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="#{'/page/' + ${totalPages}}">Utolsó</a>
<span th:unless="${currentPage < totalPages}">Utolsó</span>
</div>
</div>
Even if I comment out this part of the html, the error is still displayed.
Errors:
An error happened during template parsing (template: "class path resource [templates//users.html]")
Could not parse as expression: "{$totalItems}" (template: "/users" - line 132, col 24)
Summary:
displaying only 3 records - works
using url's like /users/page/2 - manually works
displaying the numbers and pages under the table - does not work
Well, {$totalItems} should be ${totalItems} (which is causing your parser level error). This will continue to cause a parser error regadless of if it is commented out or not unless you use Thymeleaf's parser-level comment blocks which look like this:
<!--/* Total Rows: [[{$totalItems}]] */-->
I have created a modular application based on Zero Framework following this tutorial
Because that tut didn't guide to create an Area in module Web. So I decided to create another area (named Payment) in root web project.
Areas/F1 <--my default area
Areas/Payment <--new area
I created a very simple controller (inherited from project controller base class) like this:
public class BankCodeController : FastOneControllerBase
{
// GET: Payment/BankCode
public ActionResult Index()
{
return View();
}
}
...and my Index.cshtml
#using FastOne.Web.Navigation
#{
ViewBag.CurrentPageName = PageNames.App.Payment.BankCode;
}
<div class="row margin-bottom-5">
<div class="col-xs-12">
<div class="page-head">
<div class="page-title">
<h1>
<span>BankCode</span>
</h1>
</div>
</div>
</div>
</div>
<div class="portlet light">
<div class="portlet-body">
<p>BANK CODE CONTENT COMES HERE!</p>
</div>
</div>
But I faced the error as below when I tried to access that view
The controller for path '/Payment/BankCode' was not found or does not
implement IController.
> Line 40: #RenderSection("Styles", false)
> Line 41:
> Line 42: #Html.Action("TenantCustomCss", "Layout")
> Line 43:
> Line 44: <script type="text/javascript">
Hope anyone could help me on this error some guide to create this Area in module Web project (in the tutorial). Thanksss
Update
I found this solution in ASPNET Zero forum
Since I use _Layout.cshtml of F1 area, this error happens.
I fixed it by adding F1 area name to #Html.Action usages in _Layout.cshtml.
For example
#Html.Action("Header", "Layout") becomes #Html.Action("Header", "Layout", new { area = "F1" })
#Html.Action("Sidebar", "Layout", new { currentPageName = ViewBag.CurrentPageName }) becomes #Html.Action("Sidebar", "Layout", new { currentPageName = ViewBag.CurrentPageName, area = "F1" })
I have a simple dom repeat like this:
<template is="dom-repeat" items="{{projects}}" as="project" filter="{{computeFilter(searchString)}}">
[[project.name]] - [[project.number]]
</template>
<paper-input name="filter-name" label="Filter by project name" value="{{searchString}}"></paper-input>
And there is a function that filters the projects by name:
computeFilter: function(keyword) {
if (!keyword) {
return null;
} else {
keyword = keyword.toLowerCase();
return function(project) {
var name = project.name.toLowerCase();
return (name.indexOf(keyword) != -1);
};
}
}
All good. Now, how would I go about adding another filter, if for example I'd also like ot filter by project number?
I would have another paper button binding to {{searchString2}}, but then how would I link this to a filter - in other words, how can I set up multiple filters on a dom-repeat?
There's a way to filter a dom-repeat using multiple filters.
First, here's the template:
<paper-input name="filter-name" label="Filter by project name" value="{{filterText::input}}"></paper-input>
<paper-input name="filter-name" label="Filter by project type" value="{{filterText2::input}}"></paper-input>
<template id="resultList" is="dom-repeat" items="{{ projects }}" filter="filterProject" as="project">
<div>
[[project.name]] - [[project.number]]
</div>
</template>
You must define for each filter an observer to refresh the filter:
this.$.resultList.render();
Then, you must use a filter function with your filters:
filterProject: function(item) {
return (this.filterText && item.name.match(new RegExp(this.filterText, 'i'))) ||
(this.filterText2 && item.name.match(new RegExp(this.filterText2, 'i')));
},
Here you can see an example of this method.
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'))}"
I have the following layout template:
<div id="columns" class="#View.LayoutClass">
<div id="mainColWrap">
<div id="mainCol">
#RenderBody()
</div>
</div>
#if (View.ShowLeftCol){
<div id="leftCol">
#RenderSection("LeftCol", required: false)
</div>
}
#if (View.ShowRightCol){
<div id="rightCol">
#RenderSection("RightCol", required: false)
</div>
}
</div>
If View.ShowLeftCol or View.ShowRightCol are set to false, I get the following error:
The following sections have been defined but have not been rendered for the layout page "~/Views/Shared/_Layout.cshtml": "RightCol".
I am trying to have a single layout template instead of trying to dynamically select a template at runtime. Is there a way to ignore this error and continue rendering? Can anyone think of another way to implement that would allow me to dynamically show/hide columns with Razor?
Thanks!
Was given a suggestion on the ASP.net forums that works.
Essentially, if I define #section LeftCol in my view template but don't run any code that calls RenderSection in my layout, I get the error because it doesn't get called when View.ShowLeftCol is false. The suggestion was to add an else block and essentially throw away whatever contents are in the LeftCol section.
#if (View.ShowLeftCol)
{
<div id="leftCol">
#RenderSection("LeftCol", false)
</div>
}
else
{
WriteTo(new StringWriter(), RenderSection("LeftCol", false));
}
Based on the concern raised about memory I decided to test the following out as well. Indeed it also works.
#if (showLeft)
{
<section id="leftcol">
<div class="pad">
#RenderSection("LeftColumn", false)
</div>
</section>
}
else
{
WriteTo(TextWriter.Null, RenderSection("LeftColumn", false));
}
Also, at the top of my page, this is my new logic for showLeft/showRight:
bool showLeft = IsSectionDefined("LeftColumn");
bool showRight = IsSectionDefined("RightColumn");
bool? hideLeft = (bool?)ViewBag.HideLeft;
bool? hideRight = (bool?)ViewBag.HideRight;
if (hideLeft.HasValue && hideLeft.Value == true) { showLeft = false; }
if (hideRight.HasValue && hideRight.Value == true) { showRight = false; }
Someone else said it didn't work for them, but it worked like a charm for me.
#using System.Reflection;
#{
HashSet<string> renderedSections = typeof(WebPageBase).GetField("_renderedSections", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField).GetValue(this) as HashSet<string>;
}
Then add to that hash set whatever section name you want to pretend to have rendered.
#if (View.ShowLeftCol)
{
<div id="leftCol">
#RenderSection("LeftCol", false)
</div>
}
else{ <!-- #RenderSection("LeftCol", false) --> }
easier way!!