I know about this and this, but the answers are very strange, and in any case, I am looking to understand why this particular approach does not work:
#myPackage.views.html.ol(
{
Hello
}, {
World
}
)
myPackage.views.html.ol.scala.html
#(ol:Html*)
<ol>
#for(li <- ol) {
<li>
(hi)
<span class="li-body">#li</span>
</li>
}
</ol>
Error:
not found: value Hello
I must lack fundamental understanding about the rules of the template engine, because this seems intuitively correct to me.
The # character marks the beginning of a dynamic statement, so you are no longer in template syntax. It's trying to interpret the arguments to the ol() function as straight Scala/Java code, not the template syntax.
It depends on what exactly you're trying to do, but here are two ways to do it. You could probably also use the #defining helper.
#myPackage.views.html.ol(Html("Hello"), Html("World"))
Another way is to define the Html blocks at the beginning of your view.
#html1 = { Hello }
#html2 = { <strong>World</strong> }
#main(){
#myPackage.views.html.ol(html1, html2)
}
estimatic's answer was correct.
Of what he presented, I would probably have used the #html1 = ... solution, but I found something else, which I hope will benefit some future reader.
HtmlVarArgs.scala
import play.api.templates.Html
import scala.collection.mutable.ListBuffer
class HtmlVarArgs(html: Html) {
private val templates = ListBuffer(html)
def apply(html: Html) = {
templates += html
this
}
def passTo(f: Seq[Html] => Html) = f(templates)
}
object HtmlVarArgs {
def apply(html: Html) = new HtmlVarArgs(html)
}
ol.scala.html
myPackage.views.html.ol.scala.html
#(ol:Html*)
<ol>
#for(li <- ol) {
<li>
(hi)
<span class="li-body">#li</span>
</li>
}
</ol>
And then I can use it in a template as follows:
#HtmlVarArgs {
Hello
} {
World
}.passTo(myPackage.views.html.ol.apply)
Related
I am using a partial view to list the top 5 children of a specific node.
This works, but only if I put a div before the
foreach
eg
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
<div class="title">Test</div>
<ul>
#{
var ow = #owCore.Initialise(1085);
<div> </div>
var node = Umbraco.Content(1105);
foreach (var item in node
.Children.Where("Visible")
.OrderBy("Id descending")
.Take(5)
)
{
<li>#item.pageTitle</li>
}
}
</ul>
produces the expected unsorted list.
However, if I remove the empty div
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
Test
<ul>
#{
var ow = #owCore.Initialise(1085);
var node = Umbraco.Content(1105);
foreach (var item in node
.Children.Where("Visible")
.OrderBy("Id descending")
.Take(5)
)
{
<li>#item.pageTitle</li>
}
}
The error I get is
Compiler Error Message: CS1513: } expected
Source Error:
Line 113: } Line 114: } Line 115:}
Clear looks like too few closing '}'
Presumably the div forces the closing }?
I have checked owCore (it's a library of functions I am building in App_Code : however, I have stripped this back and it's now doing nothing just to make sure there are matched curly brackets:
#using Umbraco
#using Umbraco.Core.Models
#using Umbraco.Web
#functions{
public static int Initialise(int siteDocID){
return 0;
}
}
However, if I remove the #owCore code from the partial view
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
Test
<ul>
#{
var node = Umbraco.Content(1105);
foreach (var item in node
.Children.Where("Visible")
.OrderBy("Id descending")
.Take(5)
)
{
<li>#item.pageTitle</li>
}
}
</ul>
All is ok again.
Does that mean it's definitely an issue with the owCore or simply something else tripping the issue with mismatched {}
I have checked the template calling this partial view and can't find a problem.
This doesn't make sense. Can anyone explain?
Thanks!
This is actually more of a razor question.
You start your code block with #{ and by doing that you don't need the # in front of owCore. Removing it will make it render even without the <div> as the razor parser is no longer confused by the #.
I currently have a data-sly-list that populates a JS array like this:
var infoWindowContent = [
<div data-sly-use.ed="Foo"
data-sly-list="${ed.allassets}"
data-sly-unwrap>
['<div class="info_content">' +
'<h3>${item.assettitle # context='unsafe'}</h3> ' +
'<p>${item.assettext # context='unsafe'} </p>' + '</div>'],
</div>
];
I need to add some logic into this array. If the assetFormat property is 'text/html' only then I want to print the <p> tag. If the assetFormat property is image/png then I want to print img tag.
I'm aiming for something like this. Is this possible to achieve?
var infoWindowContent = [
<div data-sly-use.ed="Foo"
data-sly-list="${ed.allassets}"
data-sly-unwrap>
['<div class="info_content">' +
'<h3>${item.assettitle # context='unsafe'}</h3> ' +
if (assetFormat == "image/png")
'<img src="${item.assetImgLink}</img>'
else if (assetFormat == "text/html")
'<p>${item.assettext # context='unsafe'}</p>'
+ '</div>'],
</div>
];
To answer your question quickly, yes you can have a condition (with data-sly-test) in your list as follows:
<div data-sly-list="${ed.allAssets}">
<h3>${item.assettitle # context='html'}</h3>
<img data-sly-test="${item.assetFormat == 'image/png'}" src="${item.assetImgLink}"/>
<p data-sly-test="${item.assetFormat == 'text/html'}">${item. assetText # context='html'}"</p>
</div>
But looking at what you're attempting to do, basically rendering that on the client-side rather than on the server, let me get a step back to find a better solution than using Sightly to generate JS code.
A few rules of thumb for writing good Sightly templates:
Try not to mix HTML, JS and CSS in the template: Sightly is on
purpose limited to HTML and therefore very poor to output JS or CSS.
The logic for generating a JS object should therefore be done in the
Use-API, by using some convenience APIs that are made fore that, like
JSONWriter.
Also avoid as much as possible any #context='unsafe', unless you filter that string somehow yourself. Each string that is not
escaped or filtered could be used in an XSS attack. This
is the case even if only AEM authors could have entered that string,
because they can be victim of an attack too. To be secure, a system
shouldn't hope for none of their users to get hacked. If you want to allow some HTML, use #context='html' instead.
A good way to pass information to JS is usually to use a data attribute.
<div class="info-window"
data-sly-use.foo="Foo"
data-content="${foo.jsonContent}"></div>
For the markup that was in your JS, I'd rather move that to the client-side JS, so that the corresponding Foo.java logic only builds the JSON content, without any markup inside.
package apps.MYSITE.components.MYCOMPONENT;
import com.adobe.cq.sightly.WCMUsePojo;
import org.apache.sling.commons.json.io.JSONStringer;
import com.adobe.granite.xss.XSSAPI;
public class Foo extends WCMUsePojo {
private JSONStringer content;
#Override
public void activate() throws Exception {
XSSAPI xssAPI = getSlingScriptHelper().getService(XSSAPI.class);
content = new JSONStringer();
content.array();
// Your code here to iterate over all assets
for (int i = 1; i <= 3; i++) {
content
.object()
.key("title")
// Your code here to get the title - notice the filterHTML that protects from harmful HTML
.value(xssAPI.filterHTML("title <span>" + i + "</span>"));
// Your code here to detect the media type
if ("text/html".equals("image/png")) {
content
.key("img")
// Your code here to get the asset URL - notice the getValidHref that protects from harmful URLs
.value(xssAPI.getValidHref("/content/dam/geometrixx/icons/diamond.png?i=" + i));
} else {
content
.key("text")
// Your code here to get the text - notice the filterHTML that protects from harmful HTML
.value(xssAPI.filterHTML("text <span>" + i + "</span>"));
}
content.endObject();
}
content.endArray();
}
public String getJsonContent() {
return content.toString();
}
}
A client-side JS located in a corresponding client library would then pick-up the data attribute and write the corresponding markup. Obviously, avoid inlining that JS into the HTML, or we'd be mixing again things that should be kept separated.
jQuery(function($) {
$('.info-window').each(function () {
var infoWindow = $(this);
var infoWindowHtml = '';
$.each(infoWindow.data('content'), function(i, content) {
infoWindowHtml += '<div class="info_content">';
infoWindowHtml += '<h3>' + content.title + '</h3>';
if (content.img) {
infoWindowHtml += '<img alt="' + content.img + '">';
}
if (content.text) {
infoWindowHtml += '<p>' + content.title + '</p>';
}
infoWindowHtml += '</div>';
});
infoWindow.html(infoWindowHtml);
});
});
That way, we moved the full logic of that info window to the client-side, and if it became more complex, we could use some client-side template system, like Handlebars. The server Java code needs to know nothing of the markup and simply outputs the required JSON data, and the Sightly template takes care of outputting the server-side rendered markup only.
Looking the at the example here, I would put this logic inside a JS USe-api to populate this Array.
I have a template that looks like this:
<lift:surround name="default" at="page-content">
<lift:bind-at name="page-title">Home</lift:bind-at>
...
</lift:surround>
The default template looks like this:
<html>
<head>
<title>Title Prefix | </title>
</head>
<body>
<h1><lift:bind name="page-title" /></h1>
<div id="page-content">
<lift:bind name="page-content" />
</div>
</body>
</html>
I want to use a snippet to replace the <title> content with a string that combines "Title Prefix" and the value of <lift:bind-at name="page-title"> (ie: "Home"). I want to contine to use that same value inside the <h1> in the <body>
How can I access a bind-at value from within a snippet that's used in the surrounding template?
I don't believe you can do what you are looking to do with bind-at directives, or at least, I haven't found a way. You should be able to use a snippet to accomplish something similar though.
For example, if you are using SiteMap, the following should be roughly equivalent.
class TitleSnippet {
//Store the value in a requestVar
private var titleVar:String = ""
def first = {
//Retrieve the title for the current Loc as defined in the Sitemap
val locTitle = for (request <- S.request;
loc <- request.location) yield loc.title
//Retrieve the text portion of the tag, and append it to the sitemap title.
//If no sitemap title exists, just display the text
"* *" #> { ns =>
val myTitle = locTitle.map{ t =>
"%s | %s".format(ns.text, t)
} openOr ns.text
titleVar = myTitle
Text(myTitle)
}
}
def title = {
"* *" #> titleVar
}
}
Then, in your template, all you'd have to do is say:
<title data-lift="TitleSnippet.first">Home</title>
So, if we had a page defined like this in the sitemap:
Menu("Sub Page 1") / "subpage"
If everything worked, you should see a title like: <title>Home | Sub Page 1</title> and if you need it elsewhere on the page, all you would have to do is: <h1 data-lift="TitleSnippet.title"></h1>.
If you need access from other snippets, you can also break out titleVar into a companion object and use a RequestVar.
Is there a way I can modify dynamically the param of a snippet?
E.g. If I call this URL
host:port/a_page?name=myname
I would like that my page look like this:
<div class="lift:surround?with=default;at=content">
<div class="lift:comet?type=MySnippet;name=myname" >
...
</div>
</div>
Is that even possible? I tried using some javascript in order to extract the param from the url and putting it in the class attribute of the div but in my understanding that won't work becase the scripts will always execute after lift framework does it's magic.
Thanks in advance! Any help is really appreciated.
I used both tips provided to make it work, like ajantis mentioned reading the param directly from snippet is the easiest way but doesnt work in a comet call. Rogach solution works.
So the solution is:
<div class="lift:Ex.wrap">
<div id="myid"></div>
</div>
def wrap = {
val name = "lift:comet?type=MySnippet;name=" + S.param("name").openOr("...")
"#myid" #> <div id="myid" class={name} ></div>
}
Why not just extract http parameter inside snippet processing? i.e.
def render = {
val name = S.param("name").openOr("...")
....
}
You can try wrapping that comet snippet in other snippet, which would transform xml and add that name=myname to class. Like:
<div class="lift:Ex.wrap">
<div class="lift:comet?type=MySnippet"></div>
</div>
class Ex {
def wrap = { (n: NodeSeq) =>
// example transformation
// AntiXML syntax
import com.codecommit.antixml._;
val comet = n \ "div" head;
val comet2 =
comet.copy(attrs = comet.attrs +
("class" -> (comet.attrs("class") + ";name=myname")))
n \ "div" updated (0, comet2) unselect
}
}
im just starting with lift and scala and have a problem i dont realy understand.
i have the folowing index.html
<html>
<head><title>title</title></head>
<body>
<table>
<lift:Members.list>
<tr>
<td><m:nick/></td>
</tr>
</lift:Members.list>
</table>
</body>
</html>
And the following snippet:
class Members {
def list(xhtml: NodeSeq) =
Member.findAll.flatMap(member => bind("m",xhtml
,"nick" -> member.nickName
))
}
for some reason i get the following error. ive tried alot of things but cant get it to work. whats wrong?
XML Parsing Error: prefix not bound to a namespace
Location: http://localhost:8080/hazardlift-1.0-SNAPSHOT/
Line Number 8, Column 25:<td><m:nick></m:nick></td>
-----------------------------^
Maybe lift doesn't get how to handle your return value. Try forcing an implicit conversion to NodeSeq by specifing it as returntype.
....
def list(xhtml: NodeSeq) : NodeSeq =
....
I just found another cause of this error - an unresolved tag.
I had this HTML:
<div >
<h3>Request Information</h3>
<lift:DetailedRequestData.renderContent>
<f:rowTag></f:rowTag>
</lift:DetailedRequestData.renderContent>
</div>
I had written this for renderContent:
def renderContent(ns: NodeSeq): NodeSeq = {
val key = beginTrans(DisplayData.logger)
var result = ns
try {
var requestID = DisplayData.getParameter("request")
bind("f", ns, "rowTag" -> <p>Request ID: {requestID}</p>)
}
catch {
case t: Throwable => DisplayData.logger.error("[DetailedRequestData$.renderContent] ", t)
}
endTrans(DisplayData.logger, key)
result
}
Since I had not assigned the result of the bind to result, I was returning the unmodified NodeSeq and got the same prefix not bound to a namespace error. Changing the one statement to:
result = bind("f", ns, "rowTag" -> <p>Request ID: {requestID}</p>)
Yes, this was my own stupid fault, but by documenting the problem here, hopefully I will save someone else from having this same problem and not knowing why.