Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Paul Griffiths 370 posts 1021 karma points
    Feb 25, 2015 @ 12:48
    Paul Griffiths
    0

    Xslt for displaying childnode subnode information

    Hi all,

    this is quite a toughie to explain what i want to achieve so ill try use some screen shots to help you understand my issue.

    So.. i have the following tree structure

    enter image description here

    Our vehicles = vehicleArea doctype Mercedes,Ford,Vauxhall = vehicleManufacturer doctype 16 seater, 8 seater, 6 seater = vehicleProfile doctype

    On the Our vehicles page i would like to display three seperate div's (for each manufacturer) and within these divs i would like to display the manufacturerName and manufacturerInformation (pulled in from from the vehicle manufacturer doc type) and also a list of each manufacturer child nodes along with a coverImage and vehicleName which is pulled from vehicleProfile doc type.

    So i have the following xslt (which may be a bit messy but kind of works)

        <xsl:param name="currentPage"/>
    <xsl:template match="/">
    
            <xsl:for-each select="$currentPage/* [@isDoc and string(umbracoNaviHide) != '1']"> 
            <article class="vehicle-list-item">
                                <xsl:if test="position() mod 4 = 0">
                        <xsl:attribute name="class">driver-profile-item last</xsl:attribute>
                    </xsl:if>
                <!--<h3 class="hidden"><xsl:value-of select="@nodeName"/></h3>-->   
                <figure>
    
                    <xsl:if test="number(manufacturerImage) &gt; 0">
                        <xsl:variable name="selectedMedia" select="umbraco.library:GetMedia(manufacturerImage, 0)" />                   
                        <img src="{$selectedMedia/umbracoFile}?width=245&amp;height=160&amp;mode=crop" alt="{$currentPage/@nodeName}" title="{@nodeName}" />
                    </xsl:if>
    
                    <xsl:if test="string(manufacturerImage) =''">                   
                        <img src="/images/awaiting-image.png?width=245&amp;height=160&amp;" alt="{$currentPage/@nodeName}" title="Awaiting image for {@nodeName}" />                    
                    </xsl:if>   
                    </figure>
    
                    <h5><xsl:value-of select="manufacturerName"/></h5> <!--a href="{umbraco.library:NiceUrl(@id)}"></a>-->
                    <xsl:if test="manufacturerInformation !=''">                    
                        <xsl:value-of select="manufacturerInformation" disable-output-escaping="yes"/>
    
    
                        <xsl:for-each select="$currentPage/VehicleManufacturer/* [@isDoc and string(umbracoNaviHide) != '1']">
                            <xsl:if test="number(coverImage) &gt; 0">
                        <xsl:variable name="coverImage" select="umbraco.library:GetMedia(coverImage, 0)" />                 
                        <img src="{$coverImage/umbracoFile}?width=245&amp;height=160&amp;mode=crop" alt="{$currentPage/@nodeName}" title="{@nodeName}" />
                            <a href="{umbraco.library:NiceUrl(@id)}"><xsl:value-of select="vehicleName"/></a>                           
                    </xsl:if>
    
                    <xsl:if test="string(coverImage) =''">                  
                        <img src="/images/awaiting-image.png?width=245&amp;height=160&amp;" alt="{$currentPage/@nodeName}" title="Awaiting image for {@nodeName}" />                    
                        <a href="{umbraco.library:NiceUrl(@id)}"><xsl:value-of select="vehicleName"/></a>                           
                    </xsl:if>
    
                        </xsl:for-each>
    
    
                    </xsl:if>
    
                    <xsl:if test="string(manufacturerInformation) =''">
                        <p>Awaiting information for </p>
                        <p><xsl:value-of select="manufacturerName"/></p>
                    </xsl:if>           
    
     </article>  
    </xsl:for-each>
    </xsl:template>
    

    My problem at the moment is the foreach loop to list the manufacturerChild nodes and inforamtion. It is just duplicating all the information and i cant work out why (this will be my lack of knowledge in xslt). This is what i am seeing

    enter image description here

    Hopefully this makes sense to someone as im really struggling to work out whats happening. Maybe i need two seperate xslt files?

    Thanks

    Paul

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Feb 25, 2015 @ 17:08
    Chriztian Steinmeier
    101

    Hi Paul,

    This is a perfect "start from scratch with match templates" situation; have a look at this, and try it in a new clean macro just to see how it works:

    <?xml version="1.0" encoding="utf-8" ?>
    <xsl:stylesheet
        version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:umbraco.library="urn:umbraco.library"
        exclude-result-prefixes="umbraco.library"
    >
    
        <xsl:output method="html" indent="yes" omit-xml-declaration="yes" />
    
        <xsl:param name="currentPage" />
        <xsl:variable name="siteRoot" select="$currentPage/ancestor-or-self::*[@level = 1]" />
    
        <xsl:template match="/">
            <!-- Start by processing the VehicleArea node, locating it from the $siteRoot -->
            <xsl:apply-templates select="$siteRoot/VehicleArea" />
        </xsl:template>
    
        <xsl:template match="VehicleArea">
            <section class="vehicles">
                <h1><xsl:value-of select="@nodeName" /></h1>
    
                <!-- Process the manufacturers -->
                <xsl:apply-templates select="VehicleManufacturer" />
    
            </section>
        </xsl:template>
    
        <xsl:template match="VehicleManufacturer">
            <div class="manufacturer">
                <h1><xsl:value-of select="@nodeName" /></h1>
    
                <!-- Process the profiles for this -->
                <xsl:apply-templates select="VehicleProfile" />
    
            </div>
        </xsl:template>
    
        <xsl:template match="VehicleProfile">
            <div class="profile">
                <h2><xsl:value-of select="@nodeName" /></h2>
    
            </div>
        </xsl:template>
    
    </xsl:stylesheet>
    

    The idea is that each template takes care of "its own" data, so to speak... does that make sense?

    Using nested for-each loops forces you to constantly place conditions etc. to make sure you have the right data, whereas the apply-templates instruction collects some items and finds the matching templates to render them with. If no items are found, nothing is rendered.

    You can have matching templates for every property as well if necessary, e.g. for rendering coverImage and manufacturerImage consistently.

    /Chriztian

  • Paul Griffiths 370 posts 1021 karma points
    Feb 25, 2015 @ 17:20
    Paul Griffiths
    0

    Hey Chriztian,

    As always thanks for taking the time out to respond to my xslt related problem :).

    I am due to leave work soon so i will try and implement this later at home and let you know how i get on. Everything that you have explained in your post seems clear enough so thanks for that.

    Ill promise to stop pestering you on twitter ha!

    Cheers

    Paul

  • Paul Griffiths 370 posts 1021 karma points
    Mar 10, 2015 @ 14:25
    Paul Griffiths
    0

    Hi Chriztian,

    Im using this code to display the vehicle profile images on the home page but i only want to display 4. I tried adding in a variable for maxitems to hold 4 and then sorting using the

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#x00A0;"> ]>
    <xsl:stylesheet 
        version="1.0" 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
        xmlns:msxml="urn:schemas-microsoft-com:xslt" 
        xmlns:umbraco.library="urn:umbraco.library" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets" xmlns:Examine="urn:Examine" xmlns:google.maps="urn:google.maps" xmlns:ucomponents.cms="urn:ucomponents.cms" xmlns:ucomponents.dates="urn:ucomponents.dates" xmlns:ucomponents.email="urn:ucomponents.email" xmlns:ucomponents.io="urn:ucomponents.io" xmlns:ucomponents.media="urn:ucomponents.media" xmlns:ucomponents.members="urn:ucomponents.members" xmlns:ucomponents.nodes="urn:ucomponents.nodes" xmlns:ucomponents.random="urn:ucomponents.random" xmlns:ucomponents.request="urn:ucomponents.request" xmlns:ucomponents.search="urn:ucomponents.search" xmlns:ucomponents.strings="urn:ucomponents.strings" xmlns:ucomponents.urls="urn:ucomponents.urls" xmlns:ucomponents.xml="urn:ucomponents.xml" 
        exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets Examine google.maps ucomponents.cms ucomponents.dates ucomponents.email ucomponents.io ucomponents.media ucomponents.members ucomponents.nodes ucomponents.random ucomponents.request ucomponents.search ucomponents.strings ucomponents.urls ucomponents.xml ">
    
    <xsl:output method="html" omit-xml-declaration="yes"/>
    
        <xsl:param name="currentPage" />
        <xsl:variable name="siteRoot" select="$currentPage/ancestor-or-self::*[@level = 1]" />
        <xsl:variable name="maxItems" select="4" />
    
        <xsl:template match="/">
            <!-- Start by processing the VehicleArea node, locating it from the $siteRoot -->        
            <xsl:apply-templates select="$siteRoot/VehicleArea" />      
        </xsl:template>
    
        <xsl:template match="VehicleArea">
                <!-- Process the manufacturers -->
                <xsl:apply-templates select="VehicleManufacturer" />
        </xsl:template>
    
        <xsl:template match="VehicleManufacturer">
               <!-- Process the profiles for this -->           
            <xsl:apply-templates select="VehicleProfile" />             
        </xsl:template>
    
    
        <xsl:template match="VehicleProfile">
    <xsl:if test="position() &lt;= $maxItems">      
                <figure class="homepage-bus">
                <xsl:if test="number(coverImage) &gt; 0">
                    <xsl:variable name="selectedMedia" select="umbraco.library:GetMedia(coverImage, 0)" />                  
                    <a href="{umbraco.library:NiceUrl(@id)}"><img src="{$selectedMedia/umbracoFile}?width=143&amp;height=120&amp;mode=crop" alt="{$currentPage/@nodeName}" title="{@nodeName}" /></a>
                    <!--<a href="{umbraco.library:NiceUrl(@id)}"><xsl:value-of select="vehicleName"/></a>   -->
                </xsl:if>
    
                <xsl:if test="string(coverImage) =''">                  
                    <a href="{umbraco.library:NiceUrl(@id)}"><img src="/images/awaiting-image.png?width=143&amp;height=120&amp;" alt="{$currentPage/@nodeName}" title="Awaiting image for {@nodeName}" /></a>
                    <!--<a href="{umbraco.library:NiceUrl(@id)}"><xsl:value-of select="vehicleName"/></a>-->
                </xsl:if>      
                    </figure>   
            </xsl:if>   
        </xsl:template>
    
    </xsl:stylesheet>
    

    Cheers

    Paul

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Mar 10, 2015 @ 14:34
    Chriztian Steinmeier
    0

    Hi Paul,

    That should actually work - assuming that you're trying to limit the display of VehicleProfile nodes, and not e.g. the VehicleManufacturer nodes?

    Are you saying that you have something like this:

    • Ford
      • 16 seater
      • 6 seater
      • 8 seater
      • 12 seater
      • 19 seater (I know... :)

    -and you're not able to cap the list at only the first four items, leaving the ridiculous 19 seater off?

    'coz that doesn't really make sense (unless you're not showing me all of the XSLT :-)

    /Chriztian

  • Paul Griffiths 370 posts 1021 karma points
    Mar 10, 2015 @ 14:48
    Paul Griffiths
    0

    Hi Chriztian.

    Yeah you got it thats exactly what i am trying to achieve here. I am trying to reduce the vehicle profile nodes to only four items so for example i now have the following tree structure

    enter image description here

    and what i am trying to do is display only four of them (thats all i have room for) on the homepage. The xslt is generating the following

    enter image description here

    it is just displaying them all. :|. I have also tried using a random class but was getting errors so took that out lol. Just want to get it working first. I was sure my attempt wasnt too far away lol

     <xsl:sort select="position() mod Exslt.ExsltMath:random()" order="descending" />
    

    Cheers

    Paul

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Mar 10, 2015 @ 15:53
    Chriztian Steinmeier
    1

    Aha :)

    I get it now - you're not trying to limit the number of profiles per vehicle, but the total number of profiles shown - across vehicles. That's suddenly a grouping problem (which is one of the worst things to ask even an experienced XSLT developer to do - just ask around :-)

    Ideally, you'd want to grab all profiles and then sort them somehow (e.g. by creation date, descending or some other criteria). This can be a potential problem because a lot of profiles will not be shown - and they can complain about it! :-)

    So do you know what the criteria will be? Should it just be the first 4, essentially just using the order in Umbraco?

    If so, you could just modify the VehicleArea template:

    <xsl:template match="VehicleArea">
            <!-- Process the manufacturers -->
            <xsl:apply-templates select="VehicleManufacturer/VehicleProfile" />
    </xsl:template>
    

    - and keep the $maxItems stuff in the VehicleProfile template.

    If you need to do sorting/grouping of some sorts, lets hear it and we'll figure that out too!

    /Chriztian

  • Paul Griffiths 370 posts 1021 karma points
    Mar 10, 2015 @ 17:57
    Paul Griffiths
    0

    Hi Chriztian,

    Basically it is my call on which vehicles i chosse to show but my thinking was to get all vehicle profiles e.g. 16 seater, ford 16 seater, E7 peugeout Taxi etc. group them together and then just randomly select any four from the group.

    I have done something similar before by setting a max item and using the random math function but i am struggling to implement this using templates.

    the code i used before looked similar to this

    i declared the variable max items and then i set it to 4

    xsl:variable name="maxItems" select="4" />
    

    i then used a for each loop with a sort function

    <xsl:sort select="position() mod Exslt.ExsltMath:random()" order="descending" />
    

    this basically looped through all the nodes randomly ordered them into position and then selected the top 8 to be displayed.

    I was thinking of something along the same lines for this site.

    Hope that makes sense

    Paul

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Mar 11, 2015 @ 07:30
    Chriztian Steinmeier
    1

    Alright — if you do that, you'll need to do the $maxItems test inside the for-each, like this:

    <xsl:template match="VehicleArea">
        <!-- Get all the profiles -->
        <xsl:variable name="vehicleProfiles" select="VehicleManufacturer/VehicleProfile" />
    
        <!-- Randomize and process the top $maxItems -->
        <xsl:for-each select="$vehicleProfiles">
            <xsl:sort select="position() mod Exslt.ExsltMath.random()" data-type="number" order="descending" />
            <xsl:if test="position() &lt;= $maxItems">
                <xsl:apply-templates select="." />
            </xsl:if>
        </xsl:for-each>
    </xsl:template>
    
    <xsl:template match="VehicleProfile">
        <figure class="homepage-bus">
            <xsl:if test="normalize-space(coverImage)">
                <!-- Do stuff -->
            </xsl:if>
        </figure>
    </xsl:template>
    

    I've had some problems with EXSLT's random() function, though - returns the same number each time it's called within the same transformation... but I guess when used with the mod operator and position(), it kinda behaves somehow randomly :)

    Otherwise, have a look at some of uComponents' random functions, which I've used a lot.

    /Chriztian

  • Paul Griffiths 370 posts 1021 karma points
    Mar 11, 2015 @ 09:31
    Paul Griffiths
    0

    Morning Chriztian,

    Thanks for getting back. As always mate your solution works perfectly. It does exactly what i need it to do. It's not really a big problem if they are not 100% randomised as long as there is some variation to give it a bit more of a dynamic feel.

    Just so i understand this full can i just ask you to explain what this line does please?

    <xsl:apply-templates select="." />
    

    Many thanks

    Paul

  • Chriztian Steinmeier 2798 posts 8788 karma points MVP 7x admin c-trib
    Mar 11, 2015 @ 09:53
    Chriztian Steinmeier
    1

    Good mornin' :-)

    No worries, the apply-templates instruction collects whatever nodes are selected by the expression in the select attribute and for each of those nodes, it finds the template to use (could be a built-in template or any of the custom ones defined in the stylesheet) and then, of course, instatiates the template.

    The dot in the select expression refers to the current context node, which in the case of being inside a for-each loop, will be the current item. So the processor will see a single VehicleProfile node and then it'll find the template that should be used for rendering it.

    The really powerful thing here is, that let's say you need to do something different depending on some property, you could just create a more specific template and the processor would automatically pick that template. For example, the way you test for a coverImage being present, could be handled by a separate template, like this:

    <!-- Vehicle template -->
    <xsl:template match="VehicleProfile">
        <figure class="homepage-bus">
            <xsl:variable name="selectedMedia" select="umbraco.library:GetMedia(coverImage, false())"/>
            <a href="{umbraco.library:NiceUrl(@id)}">
                <img src="{$selectedMedia/umbracoFile}?width=143&amp;height=120&amp;mode=crop" alt="{$currentPage/@nodeName}" title="{@nodeName}"/>
            </a>
        </figure>
    </xsl:template>
    
    <!-- Template for vehicles with no coverImage -->
    <xsl:template match="VehicleProfile[not(normalize-space(coverImage))]">
        <figure class="homepage-bus">
            <a href="{umbraco.library:NiceUrl(@id)}">
                <img src="/images/awaiting-image.png?width=143&amp;height=120&amp;" alt="{$currentPage/@nodeName}" title="Awaiting image for {@nodeName}"/>
            </a>
        </figure>
    </xsl:template>
    

    This way, you're not polluting your "standard" output template with a condition, but you can make it focus on the actual output and have another template handle the missing image case. This is usually where "traditional" programmers start to lose their grip - somehow, some people have a hard time keeping track of the almost parallel control flow that this enables, but your mileage may vary, of course.

    Hope that wasn't too much of a mouthful - have a nice day! :-)

    /Chriztian

  • Paul Griffiths 370 posts 1021 karma points
    Mar 11, 2015 @ 20:20
    Paul Griffiths
    0

    Hi Christian,

    Apologies for the late reply I been in a meeting all day!

    Thank you very much for taking the time out to write a lengthy detailed reply. I much prefer more information because it's more for me to learn and take on board :) so keep it coming ;)

    Thanks for the examples too it helps me understand things more.

    Cheers again mate! Your a legend

    Paul

Please Sign in or register to post replies

Write your reply to:

Draft