.nameJust getting into Vapor and I think I've got the basics down relatively well. But I have a design model for a page that has me stumped.
I have a simple index of servers that displays in a table just fine. What I want to do in on a per server basis lookup and count the number of related entries of another type of object, say logs, filtered by date.
It seems to be that the obvious solution is to use #embed for the summary data, but I can't find the way to send parameters to the sub template as from what I understand from the docs, the #embed inherits the context from the source page, but I want the #embed to run a filtered query based on the current server in the #for loop.
//
// WebsiteController.swift
//
import Vapor
import Leaf
struct WebsiteController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
routes.get(use: indexHandler)
}
func indexHandler(_ req: Request) -> EventLoopFuture<View> {
Server.query(on: req.db).all().tryFlatMap { servers in
let serversData = servers.isEmpty ? nil : servers
let context = IndexContext(
title: "Servers",
servers: serversData
)
return req.view.render("index", context)
}
}
}
struct IndexContext: Encodable {
let title: String
let servers: [Server]?
}
index.leaf
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>#(title) | Listing</title>
</head>
<body>
<h1>#(title)</h1>
#if(servers):
<table>
<thead>
<tr>
<th>Name #count(servers)</th>
<th>Address</th>
<th>Logs</th>
<th>Logs Today</th>
</tr>
</thead>
<tbody>
#for(server in servers):
<tr>
<td>#(server.name)</td>
<td>#(server.ip_address)</td>
<td></td>
<td></td>
</tr>
#endfor
</tbody>
</table>
#else:
<h2>No servers registered</h2>
#endif
</body>
</html>
Any ideas on how to accomplish this?
Leaf at its core is a pretty simple templating engine. This keeps its implementation simple and reduces the potential for security issues so if you want to do more than an if statement inside the for loop you'll either need to do this logic in Swift and pass it to your Leaf context, or write a custom tag.
Related
I want to display the result of sql each row from the service code to my gsp view.
My Service code is:
def health()
{
def schemaList = [:]
groovy.sql.Sql sql = new groovy.sql.Sql(dataSource);
sql.eachRow("SELECT SOURCE, count(1) as COUNT from fact group by SOURCE");
ArrayList returnResults = []
sqlStatement.eachRow(sqlString)
{
returnResults<<it.toRowResults()
}
sqlStatement.close()
return[returnMap:returnResults]
}
My Controller Code is:
def stats = {
def health = AccessLogService.heath()
render (template:'healthview', model:[health:health])
}
My gsp view is as follows:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="layout" content="admin" />
<title>Health</title>
</head>
<body>
<SCRIPT language="JavaScript">
</SCRIPT>
<br />
<br />
<font style='font-size:14px;font-weight:bold;'>Health Condition</font>
<div id='overall'>
<g:if test="${health.size() > 0}">
<table border="1">
<thead>
<tr>
<th>Source</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<g:each in="${health}" status="i" var="thisRecord">
<tr>
<td>${thisRecord.SOURCE}</td>
<td>${thisRecord.COUNT}</td>
</tr>
</g:each>
</tbody>
</table>
</g:if>
</div>
</body>
</html>
I am not able to see the results of my query in gsp view? Where I am going wrong.
you are trying to get the wrong key of your model.
you service returns a hash [returnMap:returnResults] so your controller renders the model: [health:health] -> [health:[returnMap:returnResults]].
thus in your gsp you should refer to health.returnMap to see the list:
<g:if test="${health.returnMap}">
...
<g:each in="${health.returnMap}" status="i" var="thisRecord">
<tr>
<td>${thisRecord.SOURCE}</td>
<td>${thisRecord.COUNT}</td>
</tr>
</g:each>
...
</g:if>
UPDATE:
the code looks strange... this is how it should be:
ArrayList returnResults = []
sql.eachRow("SELECT SOURCE, count(1) as COUNT from fact group by SOURCE"){
returnResults << it.toRowResults()
}
Where is the sqlStatement variable declared in your service? I think that is the error.
And an advice you need to debug your program. for example, Test if the service returns result by:
running your app debug mode
using log.debug
using println
or if you are doing these and have seen any erorrs on your console post that here.
I have the following HTML:
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Item 1</td>
<td>Description of Item 1</td>
<td>
Edit
Delete
</td>
</tr>
<tr>
<td>2</td>
<td>Item 2</td>
<td>Description of Item 2</td>
<td>
Edit
Delete
</td>
</tr>
</tbody>
</table>
The table rows (tr elements) are added dynamically.
I wire up a click event to all Edit links like this:
void wireUpTableEvents() {
var editLinks = queryAll('#order-items table tbody [data-action="edit"]');
editLinks.forEach((element) {
element.on.click.add((event){
print(element.attributes['data-item-id']);
});
});
}
As said above, the table rows (tr elements) are added dynamically so the above code only works if I call wireUpEvents after I execute the method which adds the rows.
Does anyone know the syntax or adding a event listener to elements using DART's on.click.add() when the elements are dynamcially added in the future?
I tried checking the DART documentation but the documentation on Event Listeners is blank.
If I would be using jQuery I could be using something similar to:
$("#order-items table")on("click", "tbody [data-action="edit"]", function(){...})
...but I want to write my sample app only using DART.
Edit
Though future sounds great for callbacks it seemed slightly overkill for what I needed as there is no long running task in my scenario.
The closest I was able to get to attach my event listener to a static element but processing the click events of future sub-elements was this:
void wireUpTableEvents() {
var tableBody = query('#order-items table tbody');
// Attach Event Listener to the static tbody, which always exists.
tableBody.on.click.add((event) {
var clickedElement = event.srcElement;
var itemId = clickedElement.attributes['data-item-id'];
// Check if the clicked element was either one of the edit links or one of the delete links.
switch (clickedElement.attributes['data-action']) {
case 'edit':
// Replace print with calling a method to process edit request for this item.
print('processing edit click from item with id: $itemId');
break;
case 'delete':
// Replace print with calling a method to process delete request for this item.
print('processing delete click from item with id: $itemId');
break;
}
});
}
The above code can execute before any of the actual tr elements are loaded and still works after the tr elements are loaded at some unknown later stage.
I also found that it now covers any dynamically added row, pre-loaded ones as well as other dynamically added ones for new records etc.
It sounds like you need to use Dart's Future object. John Evans has a recent post that gives an excellent overview. I'll try to give a simple example:
Let's say I have a class called htmlInDart which I call as follows:
void main() {
var htmlExample = new HtmlInDart().createStyles();
htmlExample
..then((htmlExample) => htmlExample.buildPage())
..then((htmlExample) => htmlExample.addListeners());
}
The class might look something like this:
class htmlInDart {
htmlInDart();
Future<htmlInDart> createStyles() {
final c = new Completer();
// create some styles
c.complete(this);
return c.future;
}
Future<htmlInDart> buildPage() {
final c = new Completer();
// build the page
c.complete(this);
return c.future;
}
Future<htmlInDart> addListeners() {
final c = new Completer();
// add some listeners
c.complete(this);
return c.future;
}
Hopefully this gives you some idea of how to implement it for your case.
Is there any reason you can't add the callback when you are adding the rows in the first place? Something like:
void addRow(TableElement table, ...) {
TableRowElement item = new TableRowElement();
...
table.nodes.add(item);
item.on.click.add((callback) { });
}
I am attempting to utilise KnockoutJS and MVC 4 in order to display a table with ActionLink definitions in the first column of the table. Displaying the data itself is extremely straight-forward and I'm not having any problem there. The problem I have is in the generation of the ActionLink's.
I have taken a look at Use MVC helpers inside jquery.tmpl templates, but the solution there does not utilise knockout templates and inserting the Url into the model object is not feasible (the app domain model objects used to create the view model will be used extensively through out the application).
The table definition:
<table>
<tbody data-bind="template: { name: 'dmuTableDetail', foreach: tables() }"></tbody>
</table>
(tables is an observable array, hence the parens).
The knockout template definition:
<script id="dmuTableDetail" type="text/html">
<tr>
<td>#Html.ActionLink("Details", "Details", "DMUTableCategory", new { #Id = ??? } )</td>
<td data-bind="text:TableId"></td>
<td data-bind="text:TableName"></td>
</tr>
</script>
The View Model definition:
var PageViewModel = function () {
self = this;
self.tables = ko.observableArray([]);
self.readItems = function () {
self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));
}
}
$(document).ready(function () {
vm = new PageViewModel();
self.readItems('');
ko.applyBindings(vm);
});
(the actual code performs an Ajax call to retrieve the data, but the code above also demonstrates the issue).
Regardless of what I replace the ??? with, I am unable to get the value of the TableId field to be inserted into the href.
Any help would be greatly appreciated.
Thankyou.
Thankyou Eric, you got me thinking about an anchor element and binding the href attribute.
It seems the answer is a little easier than expected (it usually is!).
The table definition: (same as original question)
<table>
<tbody data-bind="template: { name: 'dmuTableDetail', foreach: tables() }"></tbody>
</table>
The knockout template definition: (change to the binding of the href attribute).
<script id="dmuTableDetail" type="text/html">
<tr>
<td><a data-bind="attr: { 'href': '#Url.Action("Details", new RouteValueDictionary() { { "Controller", "DMUTableCategory" } } )/' + TableId }">Details</a></td>
<td data-bind="text:TableId"></td>
<td data-bind="text:TableName"></td>
</tr>
</script>?
The View Model definition: (same as original question)
var PageViewModel = function () {
self = this;
self.tables = ko.observableArray([]);
self.readItems = function () {
self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));
}
}
$(document).ready(function () {
vm = new PageViewModel();
self.readItems('');
ko.applyBindings(vm);
});
You dont actually need to RootValueDictionary but I've included it so people can see how to change the controller the request is sent to.
Knockout binds completely on the client side, which is after MVC has rendered the HTML for your page and sent it back to the original browser.
If you want your Knockout template to be able to use a URL that is generated on the server, then you'll have to employ some clever strategy similar to the following:
CSHTML:
#{
// create a dummy URL that you can use as a template
string hrefFormat = Url.Action("Details", "DMUTableCategory", new { id = "{ID}" });
}
<script type="javascript">
// a global string (but you can put it where ever you need to)
var _hrefFormat = #Html.Raw(hrefFormat)
<script>
JS:
self.readItems = function () {
self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));
// loop through the 'tables' and add a new 'href' property to each for binding
ko.utils.arrayForEach(self.tables(), function(table){
table.href = _hrefFormat.replace("{ID}", table.TableId);
});
}
Your KO Tmpl where you bind the 'href' property of each table object to the a tag's href attribute:
<script id="dmuTableDetail" type="text/html">
<tr>
<td><a data-bind="attr: { 'href': href }">Details</a></td>
<td data-bind="text:TableId"></td>
<td data-bind="text:TableName"></td>
</tr>
</script>
I've been using this guide to create PDF reporting:
http://www.codeproject.com/Articles/260470/PDF-reporting-using-ASP-NET-MVC3
Basically I have a form that once it's submitted, It creates and opens a PDF report
the report is actually a view, for example this one:
#using MvcReportGeneratorDemo.Models
#model CustomerList
<br />
<table cellpadding="3" cellspacing="3">
`<tr border="1" bgcolor="#777777" color="#ffffff">`
<td>Name</td>
<td>Address</td>
<td>Place</td>
</tr>
#foreach (Customer customer in Model)
{
<tr border="1">
<td>#customer.Name</td>
<td>#customer.Address</td>
<td>#customer.Place</td>
</tr>
}
</table>
I want that each page in the PDF will have columns headers, not just the first one.
tried google but found nothing relevant.
You can use the following code at server side to solve your problem.
using (StringReader sr = new StringReader(html))
{
foreach (IElement el in iTextSharp.text.html.simpleparser.HTMLWorker.ParseToList(sr, null))
{
if (el is PdfPTable)
{
((PdfPTable)el).HeaderRows = 1;
}
doc.Add(el);
}
}
I am trying to parse this HTML page here with HTML Agility Pack, but I cannot seem to get it to work as expected.
This is my page (shortened):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="de-ch" xml:lang="de-ch">
<head>
</head>
<body id="Adressservices">
<div id="page">
<div id="page-544">
<table class="full">
<thead>
<tr>
<th class="first" scope="col" style="width: 18%;">Type</th>
<th class="col" style="width: 20%;">Name</th>
<th class="col">Date</th>
<th class="col" style="text-align: right; width: 10%;">Size</th>
</tr>
</thead>
<tbody>
<tr>
<td class="first">Change</td>
<td>somefile01.zip</td>
<td style="width: 5%;"><b class="filesize">2012-03-01</b></td>
<td style="text-align: right;"><b class="filesize">881.00</b></td>
</tr>
<tr>
<td class="first">Change</td>
<td>somefile02.zip</td>
<td style="width: 5%;"><b class="filesize">2012-02-01</b></td>
<td style="text-align: right;"><b class="filesize">1400.00</b></td>
</tr>
<tr>.....</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>
The real page has quite a few more <tr>....</tr> rows in that table.
I was able to download the page just fine with HTML Agility Pack using this code snippet:
HtmlWeb web = new HtmlWeb();
HtmlDocument archiveDoc = web.Load(_archiveUrl);
var tables = archiveDoc.DocumentNode.SelectNodes("//table");
So I get a handle on my <table> element, works just fine.
Now I was trying to get the first <tr> element from within that table, and I tried this:
HtmlNode node = tables[0];
var allTRNodes = node.SelectNodes("tbody/tr");
var firstTR = allTRNodes[0];
Here, I'm not getting the n <tr> nodes as expected - but just two. And the first of those doesn't contain a list of y child nodes of type <td> either ...
Then I tried Linq-to-"HTML":
HtmlNode node = tables[0];
var firstTR = node.Element("tbody").Element("tr");
but again: I'm not getting the first <tr> node containing a list of y child nodes of type <td> either ...
Trying to get the list of all <td> nodes inside the first <tr> also didn't work quite as expected:
HtmlNode node = tables[0];
var allTDNodes = node.SelectNodes("tbody/tr/td");
var firstTD = allTDNodes[0];
instead of the y <td> nodes expected, I'm getting just three child nodes - two of the #text, the last one of type <td> - why??
Seems like HTML Agility Pack is misinterpreting the list of <td> nodes as nested nodes......
Any ideas? Thoughts? Hints how to solve this?
use descendant as in this example:
var linkNode = doc.DocumentNode.SelectSingle("//div[#id=\"content-wrapper\"]/dl/dd");
var hrefNode = linkNode.SelectSingleNode("descendant::a");
Something I don't agree with HtmlAgility pack that node.SelectNode* call traversing dom from the top and not from the current node.
Here's adopted sample for your case
// table
var tableNode = docNode.SelectSingleNode("//table");
// first tr
var trNode = tableNode.SelectSingleNode("descendant::tr");
// you can also try, but it's overkill
var trNode1 = tableNode.SelectSingleNode("descendant::tr[0]");
// then your td
var tdNode = trNode.SelectSingleNode("descendant::td");