How to assign dynamic HTML tag attributes in an XSLT for-each
Hi,
In my main nav (using [XSLT] Navi), I am attempting to add attributes to my anchor tags in the for-each loop and ami getting VERY strange results, like a race condition with a previously called Macro which causes the HTML ouput to be "Mixed." Here is what I am doing:
But then the UI throws an error saying "An item of type 'Attribute' cannot be constructed within a node of type 'Root'." I don't believe the loop is Ever AT the root, though, here is my for-each statement:
<xsl:for-each select="$currentPage/ancestor-or-self::node/node [@level = 2 and string(data[@alias='umbracoNaviHide']) != '1']">
What in the heck am I doing wrong here? Why can't I just write the attributes while I'm writing the link? How do I add the attributes?
Your first example should be perfectly valid (the one with the curly braces)
I'm more concerned with your xpath in the for-each. As far as I can see the statement "$currentPage/ancestor-or-self::node" is equivalent to just "$currentPage". This is because ancestor-or-self::node will search for the first xml-node of type "node" it finds starting with $currentPage, and since all xml-nodes representing documents in umbraco-xml is of type "node" your statement will always find $currentPage immediately.
I'm guessing you want to go to the current page ancestor that is on level 2 and start from there in which case your xpath should be
<xsl:for-each select="$currentPage/ancestor-or-self::node[@level='2' and @umbracoNaviHide != '1'">
Arghh - there were some errors in my previous post
There was a syntax error a "]" is missing near the end.
The ancestor-or-self::node xpath wil only select one node, so you will want to look for some nodes below the selected node appending /node[something] to the xpath.
If this doesn't help, try posting some more code to look at.
@Jesper: Using ancestor-or-self::node will in fact return a set consisting of all the node elements in the ancestor chain - not just the first. Adding the predicate [@level = 2] effectively filters the set, resulting in only one node.
@Garrett: Thus, the select you posted will actually select all level 2 nodes that aren't hidden — that might become a problem if the rest of the code assumes a single element returned...
— I'm with Jesper on the example with the curlies (or "attribute value templates" as they are called) - perfectly valid XSLT.
Christian is right about the ancestor-or-self thing, it selects all the parents, grandparents etc. of the current node. But I'm still not sure what your statement really selects, does it select all nodes below the node and its parent that is on level 2, or does it select all nodes below just one of the nodes in the ancestor tree. You could check that by inserting an
<xsl:copy-of select="$currentPage/ancestor-or-self::node/node [@level='2' and @umbracoNaviHide != '1']" />
This will emit the xml resulting from the xpath into your page output, so you can do a view source and check that the output is actually what you expect. If it is not, keep trying until you get it right.
Here's what I would do:
If you're trying to output all node at level="2" in the entire site the easiest xpath statement would probably be:
<xsl:for-each select="$currentPage/ancestor::root/descendant::node[@level='2' and ./data[@alias='umbracoNaviHide'] != '1']">
This is like saying go to the "root" node of the xml (the umbraco.config file always has a <root> node as the root), then select all descendants of type <node> that has attribute level='2' and a sub-xml-node <data alias="umbracoNaviHide"> with a value that is not "1".
This might or might not work depending on your website structure. It only works if absolutely all nodes at level 2 are nodes that is supposed to be included in the navigation. For me this is not always the case since I will frequently have a datastructure with the site itself beneath a node with nodeTypeAlias='Website' and some other types of content (sidebars, footers etc.) in folders outside the website node, but these folder will also have content at level 2
That's why I Usually have a main navigation xslt that looks like this:
This tells the xlst parser to go to the ancestor (or self) that is of nodeTypeAlias "Website" and select all nodes beneath that which isn't hidden with umbracoNavihide
But it is Literally writing the last Level 2 node links SIX times. There are definietly, positively no other Level 2 nodes after this last link, so I'm lost as to how this is happening. The links are still being written even after I'm well into the HTML output fropm the NEXT Macro in the template. For example:
Your code looks good to me. If I was having this problem, I would stop looking at the navigation-xslt, and look at the code for the next macro. Perhaps try removing the following macro and see what happens.
Could you post the code of the following macro here?
I can't see how it would be the second Macro affecting it, as this last Main nav item repeats several times no matter what HTML occurs after it, but here is the code:
<xsl:template match="/">
<xsl:for-each select="$currentPage/descendant-or-self::node [@nodeTypeAlias = 'CWS_NewsItem' and string(data [@alias='umbracoNaviHide']) != '1']">
This had nothing to do with how I was writing the <a> tag attributes or even my XSLT code. This was because the main nav links are empty elements with background images.
I am pretty sure this is an Umbraco bug: if there is no text in the <a> element, the transformation behaves erratically, as I said, writing the last node numerous times. If I have ANY text in between the <a> and </a>, everything is fine. So as a workaround, I'm using a " " there.
The bug is that the <a> element cannot be empty. This can easily be reproduced simply by creating an XSLT file using the "Navigation Prototype" selection from the dropdown, removing the <xsl:value-of select="@nodeName"/>, inserting the analogous Macro into a template, and then letting 'er rip in a browser. Check out the HTML.
Thanks so much for all your help with this, Jesper and Chriztian.
I can confirm that you get strange results when you try to add empty tags in an XSLT transformation. As a workaround, you can set your XSLT's output method to "html" instead of "xml". Then it works, though I haven't figured out why yet. Could be a bug in Umbraco.
This isn't a bug in Umbraco: As Microsoft would put it, "this behaviour is by design", and it is a feature related to Microsofts xslt parser/transformation engine not to the code in Umbraco.
When outputting xml self closing tags is allowed on any element, that is a tag looking like this <div /> is perfectly valid, a <div /> tag in (X)HTML on the other hand is something that makes most browsers choke, so whenever you're making a xslt-file where theres a risk that some tags comes out empty be sure to change the output method to html, which will render <div></div> and will always create self closing <img /> tags.
This is because XHTML is a subset of xml, with some added constraints and standards. The output method "html" makes the xslt transformation adhere to the html standards when transforming xml.
As Jesper says, this behavior is 'by design' in xslt when you're using the xml output method. Do be aware that the html output method may not be what you want either because it is html4 and not xhtml compliant. This can mess up your site's validation, for instance.
How to assign dynamic HTML tag attributes in an XSLT for-each
Hi,
In my main nav (using [XSLT] Navi), I am attempting to add attributes to my anchor tags in the for-each loop and ami getting VERY strange results, like a race condition with a previously called Macro which causes the HTML ouput to be "Mixed." Here is what I am doing:
When I take these two attributes OUT, everything outputs fine. Why can't I do this?? I also tried doing the following just above it in the loop:
But then the UI throws an error saying "An item of type 'Attribute' cannot be constructed within a node of type 'Root'." I don't believe the loop is Ever AT the root, though, here is my for-each statement:
What in the heck am I doing wrong here? Why can't I just write the attributes while I'm writing the link? How do I add the attributes?
Thanks!!!
//Garrett
Hi,
if you want to write an attribute, make sure to place it between the opening and closing tag to which you want to apply it.
HTH,
Peter
Your first example should be perfectly valid (the one with the curly braces)
I'm more concerned with your xpath in the for-each. As far as I can see the statement "$currentPage/ancestor-or-self::node" is equivalent to just "$currentPage". This is because ancestor-or-self::node will search for the first xml-node of type "node" it finds starting with $currentPage, and since all xml-nodes representing documents in umbraco-xml is of type "node" your statement will always find $currentPage immediately.
I'm guessing you want to go to the current page ancestor that is on level 2 and start from there in which case your xpath should be
Regards
Jesper Hauge
Arghh - there were some errors in my previous post
.Hauge
Thanks Jesper-- I tried the curly braces again inline w/ he anchor tag, and I get empty output. Here is the complete loop:
Same thing if I do this:
Still having a lot of trouble with this for some reason.
Thanks again,
Garrett
@Jesper: Using ancestor-or-self::node will in fact return a set consisting of all the node elements in the ancestor chain - not just the first. Adding the predicate [@level = 2] effectively filters the set, resulting in only one node.
@Garrett: Thus, the select you posted will actually select all level 2 nodes that aren't hidden — that might become a problem if the rest of the code assumes a single element returned...
— I'm with Jesper on the example with the curlies (or "attribute value templates" as they are called) - perfectly valid XSLT.
We need more code :-)
/Chriztian
Hi Garret,
Regarding your last examples - you're now using @umbracoNaviHide instead of data[@alias = 'umbracoNaviHide'] which obviously won't work...
/Chriztian
Hi again Garret
Christian is right about the ancestor-or-self thing, it selects all the parents, grandparents etc. of the current node. But I'm still not sure what your statement really selects, does it select all nodes below the node and its parent that is on level 2, or does it select all nodes below just one of the nodes in the ancestor tree. You could check that by inserting an
<xsl:copy-of select="$currentPage/ancestor-or-self::node/node [@level='2' and @umbracoNaviHide != '1']" />
This will emit the xml resulting from the xpath into your page output, so you can do a view source and check that the output is actually what you expect. If it is not, keep trying until you get it right.
Here's what I would do:
If you're trying to output all node at level="2" in the entire site the easiest xpath statement would probably be:
This is like saying go to the "root" node of the xml (the umbraco.config file always has a <root> node as the root), then select all descendants of type <node> that has attribute level='2' and a sub-xml-node <data alias="umbracoNaviHide"> with a value that is not "1".
This might or might not work depending on your website structure. It only works if absolutely all nodes at level 2 are nodes that is supposed to be included in the navigation. For me this is not always the case since I will frequently have a datastructure with the site itself beneath a node with nodeTypeAlias='Website' and some other types of content (sidebars, footers etc.) in folders outside the website node, but these folder will also have content at level 2
That's why I Usually have a main navigation xslt that looks like this:
This tells the xlst parser to go to the ancestor (or self) that is of nodeTypeAlias "Website" and select all nodes beneath that which isn't hidden with umbracoNavihide
Regards
Jesper Hauge
Hi guys, thanks again for all the help. Here is my complete code:
But it is Literally writing the last Level 2 node links SIX times. There are definietly, positively no other Level 2 nodes after this last link, so I'm lost as to how this is happening. The links are still being written even after I'm well into the HTML output fropm the NEXT Macro in the template. For example:
Strange, right? What could be causing this??
Thanks,
Garrett
Your code looks good to me. If I was having this problem, I would stop looking at the navigation-xslt, and look at the code for the next macro. Perhaps try removing the following macro and see what happens.
Could you post the code of the following macro here?
Regards
Jesper Hauge
I can't see how it would be the second Macro affecting it, as this last Main nav item repeats several times no matter what HTML occurs after it, but here is the code:
All right, I figured this out. Whew.
This had nothing to do with how I was writing the <a> tag attributes or even my XSLT code. This was because the main nav links are empty elements with background images.
I am pretty sure this is an Umbraco bug: if there is no text in the <a> element, the transformation behaves erratically, as I said, writing the last node numerous times. If I have ANY text in between the <a> and </a>, everything is fine. So as a workaround, I'm using a " " there.
The bug is that the <a> element cannot be empty. This can easily be reproduced simply by creating an XSLT file using the "Navigation Prototype" selection from the dropdown, removing the <xsl:value-of select="@nodeName"/>, inserting the analogous Macro into a template, and then letting 'er rip in a browser. Check out the HTML.
Thanks so much for all your help with this, Jesper and Chriztian.
//Garrett
I can confirm that you get strange results when you try to add empty tags in an XSLT transformation. As a workaround, you can set your XSLT's output method to "html" instead of "xml". Then it works, though I haven't figured out why yet. Could be a bug in Umbraco.
Hi Wyverex
This isn't a bug in Umbraco: As Microsoft would put it, "this behaviour is by design", and it is a feature related to Microsofts xslt parser/transformation engine not to the code in Umbraco.
When outputting xml self closing tags is allowed on any element, that is a tag looking like this <div /> is perfectly valid, a <div /> tag in (X)HTML on the other hand is something that makes most browsers choke, so whenever you're making a xslt-file where theres a risk that some tags comes out empty be sure to change the output method to html, which will render <div></div> and will always create self closing <img /> tags.
This is because XHTML is a subset of xml, with some added constraints and standards. The output method "html" makes the xslt transformation adhere to the html standards when transforming xml.
Regards
Jesper Hauge
Good to know, thanks Jesper.
At least there is an easy workaround.
As Jesper says, this behavior is 'by design' in xslt when you're using the xml output method. Do be aware that the html output method may not be what you want either because it is html4 and not xhtml compliant. This can mess up your site's validation, for instance.
You can read more on my blog at http://blog.percipientstudios.com/2009/4/11/anatomy-of-an-umbraco-xslt-file.aspx. Scroll down to the 'line 10' section that discusses the output method options and gottchas. It explains why xslt behaves the way it does as well as the pros and cons of the xml or html output methods.
cheers,
doug.
is working on a reply...