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 #.
Related
I'm trying to add nodes from one document to a new document I create, but it's not working and I don't know why. Here's the code that's going wrong:
my ($body_node) = $newdoc->findnodes('//body');
my #nodes = $source_doc->findnodes('//div[starts-with(#psname, "xyz")]');
foreach my $node(#nodes) {
$body_node = $body_node->appendChild($node);
}
$newdoc->toFile($outfile);
The code looks for some named div tags and appends them to the body tag. The problem is that it's appending them to the last div tag, not to the body tag so I'm ending up with a bunch of nested divs:
</div></div></div></div></div></div></div></div></div></div></div></div>
</div></div></div></div></div></div></div></div></div></div></div></div></body></html>
If someone could tell me what I'm doing wrong I'd be eternally grateful.
That means you probably need to come back to <body> after adding <div>:
my ($body_node) = $newdoc->findnodes('//body');
my #nodes = $source_doc->findnodes('//div[starts-with(#psname, "xyz")]');
foreach my $node(#nodes) {
$body_node = $body_node->appendChild($node);
($body_node) = $newdoc->findnodes('//body');
}
open (OUT, ">$outfile");
print OUT $newdoc->toString();
close OUT;
This has been driving me nuts - hoping someone can help me.
I have a multifield component called 'books' with a single textfield: 'title'.
Everything seems to be working; the dialog box contains the multifield then I add two title fields then enter 'title1' and 'title2'.
then in the HTML itself I go:
<div data-sly-repeat="${properties.books}">
<p>${item}</p>
<p>${itemList.index</p>
<p>${item.title}</p>
</div>
What I don't get is, ${item} correctly gives me:
{"title": "title1"} {"title": "title2"}
and ${itemList.index} correctly gives me: 0 1
but ${item.title} keeps coming up blank. I also tried ${item["title"]} and that comes up blank too.
What am I doing wrong here? In my desperation I contemplated using
<div data-title="${item}"></div>
and then using JS to process the JSON object but I don't really want to do that.
Someone help, please!
It looks like your books property is either a JSON array string or a multivalued property with each value being a JSON object string;
The easiest way to parse the property is via a JS model like the following:
You could simplify this script to match your specific case, I made it general to multi-value and non-multi-value string properties.
/path/to/your-component/model.js:
"use strict";
use(function () {
// parse a JSON string property, including multivalued, returned as array
function parseJson(prop){
if(!prop) return [];
var result =[];
if(prop.constructor === Array){
prop.forEach(function(item){
result.push(JSON.parse(item));
});
}
else {
var parsed = JSON.parse(prop);
if(parsed.constructor === Array){
result = parsed;
}
else result = [parsed];
}
return result;
}
var $books = properties.get("books", java.lang.reflect.Array.newInstance(java.lang.String, 1));
var books = parseJson($books);
return {
books: books
}
});
/path/to/your-component/your-component.html:
<sly data-sly-use.model="model.js"/>
<div data-sly-repeat="${model.books}">
<p>${item}</p>
<p>${itemList.index</p>
<p>${item.title}</p>
</div>
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 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)
I have the below (simplified) code, which uses the following source:
<html>
<p>line 1</p>
<div>
<a>line 2</a>
</div>
</html>
soup = BeautifulSoup('<html><p>line 1</p><div><a>line 2</a></div></html>')
ele = soup.find('p').nextSibling
somehow_print_tag_of_ele_here
I want to get the tag of ele, in this case "div". However, I only seem to be able to get the tag of its children. Am I missing something simple? I thought that I could do ele.tag.name, but that is an exception since tag is None.
#Below correctly prints the div element "<div><a>line 2</a></div>"
print ele
#Below prints "None". Printing tag.name is an exception since tag is None
print ele.tag
#Below prints "a", the child of ele
allTags = ele.findAll(True)
for e in allTags:
print e.name
At this point, I am considering doing something along the way of getting the parent of ele, then getting the tags of parent's children and, having counted how many upper siblings ele has, counting down to the correct child tag. That seems ridiculous.
ele is already a tag, try doing this:
soup = BeautifulSoup('<html><p>line 1</p><div><a>line 2</a></div></html>')
print(soup.find('p').nextSibling.name)
so in your example it would be just
print(ele.name)
You can access anything inside an element as if accessing a dictionary.
Let's say you have an element like this one.
<input id="__VIEWSTATE3" name="__VIEWSTATE3" type="hidden" value="MwqzeTH4"/>
You can access each property like this
print(elem["id"])
# prints __VIEWSTATE3
print(soup.find('h1',id_='pdp_product_title'))
it doesnot print any detail please solved this
<h1 id="pdp_product_title" class="headline-2 css-zis9ta" data-test="product-title">Nike Air Force 1 Shadow</h1>