Set variable in foreach and use it outside the foreach
Hello,
I'm writing an XSLT file in which I need to set a variable. This variable can be fetched from a querystring. If the value is empty it needs to be set at a later moment. In a foreach loop I need to get the first value if the querystring is empty. After this I need the value outside of the foreach loop. I can't figure out how to do this since a variable can only be set once (or only used inside the foreach loop). Any idea's how to solve this?
If the for-each is semi intensive, it might be worth doing the for-each and storing the result in it's own variable first, then do 2 less intensive for-each loops, one inside the variable definition, and one in the body.
That would be a solution. I might be able to solve it with an Xpath statement, but the foreach loop also has pages (and I need the first Id of loaded page) so I would need to do that inside the Xpath. In the loop I'm also calling templates so I would need to loop twice because I can't do it at the same time.
Well it's got to do with the XSLT jQuey paging sample you helped me with :). I need to get the id of the first row displayed in a page. So if the page is 2 I need id of the first row displayed. Here is some code:
<!-- The nodes which will be displayed. -->
<xsl:variable name="nodes" select="$currentPage/node [@nodeTypeAlias = 'NewsItem']" />
<!-- Get the newsId from the querystring. If it's empty get the first id from the displayed page. -->
<xsl:variable name="newsId" >
<xsl:choose>
<xsl:when test="umbraco.library:RequestQueryString('newsId') <= 0 or string(umbraco.library:RequestQueryString('newsId')) = '' or string(number(umbraco.library:RequestQueryString('newsId'))) = 'NaN'">
<!-- This XPath needs to be updated -->
<xsl:value-of select="$nodes[2]/@id" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="umbraco.library:RequestQueryString('newsId')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- Loop through items ordered by date descending. -->
<xsl:for-each select="$nodes">
<xsl:sort order="descending" select="data[@alias = 'date']" data-type="text"/>
<xsl:sort order="descending" select="@createDate" data-type="text"/>
<xsl:if test="position() > ($itemsPerPage * $pageIndex) and position() <= number(($itemsPerPage * $pageIndex) + $itemsPerPage)">
<!-- Show the news item. -->
<xsl:call-template name="create-news-item">
<xsl:with-param name="node" select="." />
</xsl:call-template>
</xsl:if>
</xsl:for-each>
This works, but as you might have noticed the foreach uses a sort so I get back a value but it's not the correct value because I need the same sorting on the XPath. Any idea how that's possible?
Yep thats the way to go, sort your nodes first and store the sorted nodes in a variable. Then you can use you xpath on the sorted nodes in the variable.
Like mate says you "may" need to use msxml:node-set( $nodes ) to convert to a node-set. I never remember the rule for when to do this so tend to try it out first. Really should read up on the whys and wheres for node-set function usage.
Could be the node-set issue. When you sort XML in a variable it needs to be stored as a "node-set" if you want to do anything useful with it. Luckily MS gives us a way to convert our XML fragments into node sets using the node-set function. So you could do:
That was a typo. Unfortunately it doesn't solve my problem. I still get the same error. It's not defind and because of that none of the results returned are properly.
This returns 1 because the code doens't work and somehow I get a 1 back. If I debug count(msxml:node-set($nodes2)) I get an exception but the variable gets set to 1. Any suggestions?
Well I don't know what goes wrong so I used Matt his example. Here is the code:
<!-- Get the newsId from the querystring. If it's empty get the first id from the displayed page. -->
<!-- This code could be better because now we loop twice through the loops. -->
<xsl:variable name="newsId">
<xsl:choose>
<xsl:when test="umbraco.library:RequestQueryString('newsId') <= 0 or string(umbraco.library:RequestQueryString('newsId')) = '' or string(number(umbraco.library:RequestQueryString('newsId'))) = 'NaN'">
<xsl:for-each select="$nodes">
<xsl:sort order="descending" select="data[@alias = 'date']" data-type="text"/>
<xsl:sort order="descending" select="@createDate" data-type="text"/>
<xsl:if test="position() > ($itemsPerPage * $pageIndex) and position() <= number(($itemsPerPage * $pageIndex) + $itemsPerPage)">
<xsl:if test="position() = ($itemsPerPage * ($pageIndex)+1)">
<xsl:value-of select="@id"/>
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="number(umbraco.library:RequestQueryString('newsId'))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Too bad I couldn't solve it properly, but I'm out of time and this works for now.
I got it solved! After looking at a sample my colleague used I discovered what I was missing.
Here is how it's done:
<!-- The nodes which will be displayed. Sorted in the correct order. -->
<xsl:variable name="temp_nodes">
<xsl:for-each select="$currentPage/node [@nodeTypeAlias = 'NewsItem']">
<xsl:sort order="descending" select="data[@alias = 'date']" data-type="text"/>
<xsl:sort order="descending" select="@createDate" data-type="text"/>
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
<!-- Convert the nodes to a which is easier to work with. -->
<xsl:variable name="nodes" select="msxml:node-set($temp_nodes)/node" />
<!-- The total amount of items -->
<xsl:variable name="totalItems" select="count($nodes)" />
I was missing the /node after the msxml:node-set. Strange enough if you try to look at the value in the debugger you get an error like in the screens I posted earlier. Despite you can't look at the value in the debugger the code still works. This solved my problem :).
I'd like to kick this topic again :). 3 posts up is how I solved my problem, but this solution doens't work in Umbraco 4.5. How can I do the following in 4.5?
<!-- Convert the nodes to a way which is easier to work with. --> <xsl:variablename="nodes"select="msxml:node-set($temp_nodes)/node"/>
The /node doesn't work anymore since each element has a new name. How should this be done in 4.5?
I've not played to much with 4.5, but something like this should work
<!-- The nodes which will be displayed. Sorted in the correct order. --> <xsl:variable name="temp_nodes"> <xsl:for-each select="$currentPage/NewsItem"> <xsl:sort order="descending" select="date" data-type="text"/> <xsl:sort order="descending" select="@createDate" data-type="text"/> <xsl:copy-of select="." /> </xsl:for-each> </xsl:variable>
<!-- Convert the nodes to a which is easier to work with. --> <xsl:variable name="nodes" select="msxml:node-set($temp_nodes)/NewsItem" />
<!-- The total amount of items --> <xsl:variable name="totalItems" select="count($nodes)" />
Set variable in foreach and use it outside the foreach
Hello,
I'm writing an XSLT file in which I need to set a variable. This variable can be fetched from a querystring. If the value is empty it needs to be set at a later moment. In a foreach loop I need to get the first value if the querystring is empty. After this I need the value outside of the foreach loop. I can't figure out how to do this since a variable can only be set once (or only used inside the foreach loop). Any idea's how to solve this?
Jeroen
Hi Jeroren,
I think you are going to have to do the foreach within the variable element
Many thanks
Matt
If the for-each is semi intensive, it might be worth doing the for-each and storing the result in it's own variable first, then do 2 less intensive for-each loops, one inside the variable definition, and one in the body.
Many thanks
Matt
Hi Matt,
That would be a solution. I might be able to solve it with an Xpath statement, but the foreach loop also has pages (and I need the first Id of loaded page) so I would need to do that inside the Xpath. In the loop I'm also calling templates so I would need to loop twice because I can't do it at the same time.
Jeroen
Hey Jeroen,
Not sure what you mean by "Pages", but maybe if you posted an example peice of XML, we could write a XPath statemnt that avoids a for-each?
Matt
Hi Matt,
Well it's got to do with the XSLT jQuey paging sample you helped me with :). I need to get the id of the first row displayed in a page. So if the page is 2 I need id of the first row displayed. Here is some code:
Hope you can help me with an XPath.
Jeroen
Hey Jeroen,
Maybe try this as the selector:
That should get the first node for the given page (so long as you have done all the paging stuff before hand obviously)
Matt
Hi Matt,
That alsmost worked. Now it's this:
This works, but as you might have noticed the foreach uses a sort so I get back a value but it's not the correct value because I need the same sorting on the XPath. Any idea how that's possible?
Jeroen
Lol, i wasn't sure wheather it needed plus 1 =)
Ok, maybe for your nodes, try this
Not sure whether you will then need to use the node-set() method to convert it back into a nodeset or not, but you can give it a try.
But you should then be able to skip the sorting in the body.
Matt
Yep thats the way to go, sort your nodes first and store the sorted nodes in a variable. Then you can use you xpath on the sorted nodes in the variable.
Like mate says you "may" need to use msxml:node-set( $nodes ) to convert to a node-set. I never remember the rule for when to do this so tend to try it out first. Really should read up on the whys and wheres for node-set function usage.
Good fix Matt, gets my vote!
Hi Matt,
Is it possible the values are stored differently in the variable now? Your code works, but after this I try a count and this throws an error.
This used to work, but now it doesn't. It just throws an error and I don't know what's going wrong. Thanks for all the help so far.
Jeroen
Yea, thats when the node-set method comes in, try:
Matt
Error message?
Could be the node-set issue. When you sort XML in a variable it needs to be stored as a "node-set" if you want to do anything useful with it. Luckily MS gives us a way to convert our XML fragments into node sets using the node-set function. So you could do:
Can get a bit messy so I tend to convert it once, first build you variable up using a temp variable then convert that once into your real variable:
That should do it and its much clear to read too.
Ooops, missed a braket
Matt
Good idea Pete, putting into a temp var first and only doing the cast once.
Matt
PS, you missed a dolar sign in your temp var reference
Matt
Thanks for all the great replies. This is all pretty new to me and I'm still getting errors. Now I get the following error if I try
msxml:node-set($temp_nodes) The function msxml:node-set() is not defined
Do I need to include something extra?
Jeroen
Hey Jeroen,
Make sure you have the following in your xslt:stylesheet declaration:
Also make sure you add msxml to the exclude-result-prefixes attribute too
Matt
Hi Matt,
I think I already did this. Here is the top of my XSLT:
Jeroen
Hmmm, that is weird, and you are calling msxml:node-set(...) within that XSLT file and not the include?
Matt
I'm calling from withing the file. Here is my entire XSLT:
I don't know what's going wrong.
Jeroen
count(nodes3) needs to be count($nodes3) but not sure whether that would throw the exception you mentioned
Matt
Hi Matt,
That was a typo. Unfortunately it doesn't solve my problem. I still get the same error. It's not defind and because of that none of the results returned are properly.
This returns 1 because the code doens't work and somehow I get a 1 back. If I debug count(msxml:node-set($nodes2)) I get an exception but the variable gets set to 1. Any suggestions?
Jeroen
Hmmmm, really not sure on that one then. Could do with knowing more about the exception.
What version of .NET are you using? Not that I'm aware of any versioning issues.
Matt
I'm using .NET 3.5 and the only thing the exception show is 'The function msxml:node-set() is not defined'.
Here are some debug images with info:
1 execute the following code
2 Result in the debugger: http://img9.imageshack.us/f/77430599.jpg/
3 Try to do the conversion:
4 Watching the select in the debugger: http://img241.imageshack.us/f/45058934.jpg/
So that's what's happening. If I can't get this solved I'll go with the first idea you had Matt. I'll set the id inside the variable.
Jeroen
Well I don't know what goes wrong so I used Matt his example. Here is the code:
Too bad I couldn't solve it properly, but I'm out of time and this works for now.
Jeroen
I got it solved! After looking at a sample my colleague used I discovered what I was missing.
Here is how it's done:
I was missing the /node after the msxml:node-set. Strange enough if you try to look at the value in the debugger you get an error like in the screens I posted earlier. Despite you can't look at the value in the debugger the code still works. This solved my problem :).
Jeroen
Thats strange, but hey, glad you managed to get it working.
Matt
Hey Jeroen,
You may want to think about marking a post as an answer, as the topic got pretty long =)
Matt
I'd like to kick this topic again :). 3 posts up is how I solved my problem, but this solution doens't work in Umbraco 4.5. How can I do the following in 4.5?
The /node doesn't work anymore since each element has a new name. How should this be done in 4.5?
Jeroen
You no longer have generic nodes called "node", so you have to provide either the nodename (i.e. the old alias) instead, or a wildcard char, e.g. '*'
e.g.
or
Hey Jeroen,
I've not played to much with 4.5, but something like this should work
Check out here for differences in schema
http://our.umbraco.org/wiki/reference/xslt/45-xml-schema
Matt
Hi Jeroen,
Best (if you know which DocumentTypes - call them by name):
Good:
/Chriztian
Wow fast responses!
@Tommy Poulsen @Matt Brailsford @Chriztian Steinmeier thanks for your reactions. This solved my problem :).
To replace the /node I now use the /*[@isDoc] since this does the exact same thing.
Jeroen
is working on a reply...