I'm sure you also don't like the "messiness" of all the times you need to output "fake" div tags etc... there must be another way, right?
Here's my take on this problem - although it may seem very different from what you'd normally see, it's a much cleaner way to handle stuff like this - ask me anything you want about it, if you want to understand what's going on.
<xsl:param name="currentPage" />
<!-- This is the number of initial items -->
<xsl:variable name="offset" select="2" />
<!-- This the number of nodes to group subsequently -->
<xsl:variable name="groupSize" select="3" />
<xsl:template match="/">
<!-- Process the MNTP property -->
<xsl:apply-templates select="$currentPage/homeFeatureBlock" />
</xsl:template>
<!-- Template for the MNTP property -->
<xsl:template match="homeFeatureBlock">
<!-- First group -->
<div class="initial-row">
<xsl:apply-templates select="MultiNodePicker/nodeId[position() <= $offset]" />
</div>
<!-- Subsequent groups -->
<xsl:for-each select="MultiNodePicker/nodeId[((position() + $offset - 1) mod $groupSize) = 1]">
<div class="grouped-row">
<xsl:apply-templates select=". | following-sibling::nodeId[position() < $groupSize]" />
</div>
</xsl:for-each>
</xsl:template>
<!-- Helper to get the referenced node -->
<xsl:template match="nodeId">
<xsl:apply-templates select="id(.)" />
</xsl:template>
<!-- Generic template for a block -->
<xsl:template match="*[@isDoc]">
<div class="block">
<h4><xsl:value-of select="@nodeName" /></h4>
<p><xsl:value-of select="blockDescription" disable-output-escaping="yes"/></p>
</div>
</xsl:template>
It works great. I just have to understand it now. I have a few questions if you don't mind and a few problems I've ran into.
I stripped out some code for posting the problem, and im having trouble fitting it back into the solution you gave me.
The problem I'm having is that I need to apply a different class to the blocks depending on if its the first row or subsequent rows.
So my block template would look like..
<div class="2 column block"> for each of the "initial row" blocks, and <div class="3 column block"> for the subsequent blocks.
Is there away to have two templates and set which template block to use?
Im also having trouble with applying an image to the block I had.
Im not all that competent with XSLT, so if you could help my understanding of what you did on a few points, thanks. Ive added my question to the code, if thats ok?
Thanks for your help on this.
<xsl:paramname="currentPage"/>
<!-- This is the number of initial items --> <xsl:variablename="offset"select="2"/>
<!-- This the number of nodes to group subsequently --> <xsl:variablename="groupSize"select="3"/>
<xsl:templatematch="/">
<!-- Process the MNTP property --> <xsl:apply-templatesselect="$currentPage/homeFeatureBlock"/>
</xsl:template>
<!-- Template for the MNTP property --> <xsl:templatematch="homeFeatureBlock"> <!-- First group --> <divclass="initial-row"> <xsl:apply-templatesselect="MultiNodePicker/nodeId[position() <= $offset]"/><!--OK, I GET THIS. IF POSITION OF NODE IS LESS THAN THE VARIABLE OFFSET.--> </div>
<!-- Subsequent groups --> <xsl:for-eachselect="MultiNodePicker/nodeId[((position() + $offset - 1) mod $groupSize) = 1]"><!--IM SORRY, CAN YOU EXPLAIN THIS--> <divclass="grouped-row"> <xsl:apply-templatesselect=". | following-sibling::nodeId[position() < $groupSize]"/><!--AND THIS.--> </div> </xsl:for-each> </xsl:template>
<!-- Helper to get the referenced node --> <xsl:templatematch="nodeId"> <xsl:apply-templatesselect="id(.)"/><!--IS THIS APPLYING THE TEMPLATE BELOW? WHAT EXACTLY ODES IT SAY IN LAYMENS TERMS?--> </xsl:template>
<!-- Generic template for a block --> <xsl:templatematch="*[@isDoc]"> <divclass="block"> <xsl:variable name="image" select="blockImage[normalize-space()]"/> <xsl:choose> <xsl:when test="$image !=''"> <xsl:apply-templates select="$image" /> </xsl:when> <xsl:otherwise> <img src="/media/7832/defaultpixel.png"/> </xsl:otherwise> </xsl:choose> <h4><xsl:value-ofselect="@nodeName"/></h4> <p><xsl:value-ofselect="blockDescription"disable-output-escaping="yes"/></p> <!-- Output the URL using umbraco.library's NiceUrl method --> <a class="morelink" href="{umbraco.library:NiceUrl(.)}"><!--IM HAVING SOME TROUBLE HERE OUTPUTTING AN NICEURL--> <xsl:text>more information</xsl:text> </a> </div> </xsl:template>
<xsl:for-each select="MultiNodePicker/nodeId[((position() + $offset - 1) mod $groupSize) = 1]">
MultiNodePicker/nodeId selects all the <nodeId> elements, which we will then filter...
(position() + $offset - 1) gets us to the first one after the ones we started with.
Using the modulos operator ((...) mod $groupSize) = 1 we select the 1st of every group of $groupSize elements following.
Now we have a set of nodes that should all start a group, so we create the <div class="grouped-row"> and then we tell the processor to create a new set of nodes, combining the current one (.) and its following $groupSize siblings (to create the content inside the group):
This will execute the nodeId template for those items - because the <nodeId> elements are just references we need to get the actual Umbraco nodes - we could do this with umbraco.library:GetXmlNodeById() - but this will "explode" if a referenced node gets unpublished or deleted, so instead I use a built-in function - id(), which will just return an empty node set if the referenced node doesn't exist. (There's a couple of things that has to be right for this to work, but when used in Umbraco, inside a template for an element that exist in the XML, it's safe to use.)
For the last one - outputting the URL - you just need to send in the @id attribute, because at this point you're inside a document template, so `.` will refer to the XML node itself - so:
Thanks again for the help. After a few reads, it's starting to make sense, especially using the built in function for id(). Ive had that "explore" problem before. My class names were really for illustration. I'll need to learn to publish exactly what i have infront of me. However Im still having an issue with apply the classes.
<!-- This is the number of initial items -->
<xsl:variable name="offset" select="2" />
<!-- This the number of nodes to group subsequently -->
XSLT Group Nodes In 2 or 3
Hi,
Im looking for some help with grouping my nodes.
I have a multi node tree picker set up and outputting my nodes correctly.
What I would like is to group / wrap a div, around the first two nodes and then group subsequent nodes in threes.
Any help would be grateful.
The xslt I have so far is...
Hi Martin,
I'm sure you also don't like the "messiness" of all the times you need to output "fake" div tags etc... there must be another way, right?
Here's my take on this problem - although it may seem very different from what you'd normally see, it's a much cleaner way to handle stuff like this - ask me anything you want about it, if you want to understand what's going on.
/Chriztian
Thanks Chriztian.
It works great. I just have to understand it now. I have a few questions if you don't mind and a few problems I've ran into.
I stripped out some code for posting the problem, and im having trouble fitting it back into the solution you gave me.
The problem I'm having is that I need to apply a different class to the blocks depending on if its the first row or subsequent rows.
So my block template would look like..
<div class="2 column block"> for each of the "initial row" blocks, and <div class="3 column block"> for the subsequent blocks.
Is there away to have two templates and set which template block to use?
Im also having trouble with applying an image to the block I had.
Im not all that competent with XSLT, so if you could help my understanding of what you did on a few points, thanks. Ive added my question to the code, if thats ok?
Thanks for your help on this.
Hi Martin,
A little about the tricky bits:
MultiNodePicker/nodeId selects all the <nodeId> elements, which we will then filter...
(position() + $offset - 1) gets us to the first one after the ones we started with.
Using the modulos operator ((...) mod $groupSize) = 1 we select the 1st of every group of $groupSize elements following.
Now we have a set of nodes that should all start a group, so we create the <div class="grouped-row"> and then we tell the processor to create a new set of nodes, combining the current one (.) and its following $groupSize siblings (to create the content inside the group):
This will execute the nodeId template for those items - because the <nodeId> elements are just references we need to get the actual Umbraco nodes - we could do this with umbraco.library:GetXmlNodeById() - but this will "explode" if a referenced node gets unpublished or deleted, so instead I use a built-in function - id(), which will just return an empty node set if the referenced node doesn't exist. (There's a couple of things that has to be right for this to work, but when used in Umbraco, inside a template for an element that exist in the XML, it's safe to use.)
For the last one - outputting the URL - you just need to send in the @id attribute, because at this point you're inside a document template, so `.` will refer to the XML node itself - so:
Hope that clears some confusion for you !
/Chriztian
Regarding the class names - first of all, "2" and "3" are not valid CSS class names, so you should be prepared for problems with those.
You can send an additional parameter to the template that handles this, e.g. when rendering the "initial" blocks:
Then add the parameter to the template with a default value (so you don't need to change the 3-column version):
/Chriztian
Hi Chriztian,
Thanks again for the help. After a few reads, it's starting to make sense, especially using the built in function for id(). Ive had that "explore" problem before.
My class names were really for illustration. I'll need to learn to publish exactly what i have infront of me.
However Im still having an issue with apply the classes.
Sorry, just noticed that the styling of the code has gone weird.
Yeah that's a little hard to read :-)
Ok - so what's you problem exactly? Can you show us what you want to get and what you are getting instead?
/Chriztian
Hi Chriztian,
Thanks again for your help.
Sorry, my post doesn't seem to have the format option of "code", only preformated, which makes it go weird. Ive just left the code as text.
I would like the first two nodes to be outputted like this
<!-- FIRST 2 NODES -->
<div class="initial-row row clearfix">
<div class="eight columns block b-green">
<img src="/block-one-image.png" alt="Block One" />
<h4>Block One</h4>
<a href="/block-one/">More</a>
</div>
<div class="eight columns block b-blue">
<img src="/block-two-image.png" alt="Block Two" />
<h4>Block Two</h4>
<a href="/block-two/">More</a>
</div>
</div>
And the remaining nodes to be outpuuted in groups of three.
<!-- FIRST ROW OF 3-->
<div class="grouped-row row clearfix">
<div class="one-third column block b-red">
<img src="/block-three-image.png" alt="Block Three" />
<h4>Block Three</h4>
<a href="/block-one/">More</a>
</div>
<div class="one-third column block b-yellow">
<img src="/block-four-image.png" alt="Block Four" />
<h4>Block Four</h4>
<a href="/block-four/">More</a>
</div>
<div class="one-third column block b-purple">
<img src="/block-five-image.png" alt="Block Five" />
<h4>Block Five</h4>
<a href="/block-five/">More</a>
</div>
</div>
<!-- SECOND ROW OF 3-->
<div class="grouped-row row clearfix">
<div class="one-third column block b-white">
<img src="/block-six-image.png" alt="Block Six" />
<h4>Block Six</h4>
<a href="/block-six/">More</a>
</div>
<div class="one-third column block b-orange">
<img src="/block-seven-image.png" alt="Block Seven" />
<h4>Block Seven</h4>
<a href="/block-seven/">More</a>
</div>
<div class="one-third column block b-black">
<img src="/block-eight-image.png" alt="Block Eight" />
<h4>Block Eight</h4>
<a href="/block-eight/">More</a>
</div>
</div>
What is happening is that the "initial-row" blocks are outputting with the class of "eight columns", instead of "one-third column".
The XSLT I have is
<xsl:param name="currentPage" />
<!-- This is the number of initial items -->
<xsl:variable name="offset" select="2" />
<!-- This the number of nodes to group subsequently -->
<xsl:variable name="groupSize" select="3" />
<xsl:template match="/">
<!-- Process the MNTP property -->
<xsl:apply-templates select="$currentPage/homeFeatureBlock" />
</xsl:template>
<!-- Template for the MNTP property -->
<xsl:template match="homeFeatureBlock">
<!-- First group -->
<div class="initial-row row clearfix">
<xsl:apply-templates select="MultiNodePicker/nodeId[position() <= $offset]" >
<xsl:with-param name="class" select="'eight columns block'" />
</xsl:apply-templates>
</div>
<!-- Subsequent groups -->
<xsl:for-each select="MultiNodePicker/nodeId[((position() + $offset - 1) mod $groupSize) = 1]">
<div class="grouped-row row clearfix">
<xsl:apply-templates select=". | following-sibling::nodeId[position() < $groupSize]" />
</div>
</xsl:for-each>
</xsl:template>
<!-- Helper to get the referenced node -->
<xsl:template match="nodeId">
<xsl:apply-templates select="id(.)" />
</xsl:template>
<!-- Generic template for a block -->
<xsl:template match="*[@isDoc]">
<xsl:param name="class" select="'one-third column block'" />
<div class="{$class}">
<xsl:variable name="image" select="blockImage[normalize-space()]"/>
<xsl:choose>
<xsl:when test="$image !=''">
<xsl:apply-templates select="$image" />
</xsl:when>
<xsl:otherwise>
<img src="/media/7832/defaultpixel.png"/>
</xsl:otherwise>
</xsl:choose>
<h4><xsl:value-of select="@nodeName" /></h4>
<p><xsl:value-of select="blockDescription" disable-output-escaping="yes"/></p>
<!-- Output the URL using umbraco.library's NiceUrl method -->
<a class="morelink" href="{umbraco.library:NiceUrl(@id)}">
<xsl:text>more information</xsl:text>
</a>
</div>
</xsl:template>
<!-- Image for block -->
<xsl:template match="Image">
<img src="{umbracoFile}" alt="{@nodeName}" />
</xsl:template>
is working on a reply...