Copied to clipboard

Flag this post as spam?

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


  • Sam 184 posts 209 karma points
    Dec 08, 2010 @ 17:18
    Sam
    0

    Create list of media items for download

    Hi everyone,

    Could someone point me in the right direction in regards to creating a list from the following structure:

    (MEDIA SECTION STRUCTURE)

    rootDocFolder (alias on a media picker on doctype called downloads)

    --Docs

    ----Document1.doc

    ----Document2.pdf

    --Forms

    ----Form1.doc

    ----Form2.pdf

     

    <xsl:variable name="mediaFolder" select="umbraco.library:GetMedia($currentPage/data [@alias='rootDocFolder'], 'true')" />        
    <xsl:for-each select="umbraco.library:GetMedia($mediaFolder, 'true')/node">
     <h2><xsl:value-of select="$mediaFolder/@nodeName"/></h2>
    <ul>
    <li>
    list-item-here
    </li>
    </ul>
    </xsl:for-each>

     

    This is what I have so far, I'm trying to achieve a header (which is the folder name) and a list underneath (child nodes/documents in media section). There will be more than one folder with different docs in. I am trying to create a simple downloads section.

    Thanks in advance if anyone can point me in the right direction, I am a bit confused with umbraco.library:GetMedia and how to use it properly.

    Sam.

     

  • Chriztian Steinmeier 2799 posts 8789 karma points MVP 8x admin c-trib
    Dec 08, 2010 @ 22:37
    Chriztian Steinmeier
    1

    Hi Sam,

    Here's something to get you started - based on the snippet you posted it looks like you're using the old XML schema; you should really look into the new schema from Umbraco 4.5, because the XSLT will make much more sense in terms of readability - but here goes for the old schema:

    <?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="xml" indent="yes" omit-xml-declaration="yes" />
    
        <xsl:param name="currentPage" />
    
        <xsl:template match="/">
            <!-- This makes sure to only do something if there is a value in the property -->
            <xsl:apply-templates select="$currentPage/data[@alias = 'rootDocFolder'][normalize-space()]" />
        </xsl:template>
    
        <!-- Template for the property -->
        <xsl:template match="data[@alias = 'rootDocFolder']">
    <!-- Fetch the media XML and start the processing --> <xsl:variable name="mediaFolder" select="umbraco.library:GetMedia(., true())" /> <!-- ...but only if we did not get an error back --> <xsl:apply-templates select="$mediaFolder[not(error)]" /> </xsl:template> <!-- Template for the root folder --> <xsl:template match="node[@nodeTypeAlias = 'Folder']"> <h3><xsl:value-of select="@nodeName" /></h3> <ul> <!-- Render all Files below this, regardless of level --> <xsl:apply-templates select="descendant::node[@nodeTypeAlias = 'File']" /> </ul> </xsl:template> <!-- Template for a file --> <xsl:template match="node[@nodeTypeAlias = 'File']"> <li> <a href="{data[@alias = 'umbracoFile']}"> <xsl:value-of select="@nodeName" /> </a> </li> </xsl:template> <!-- Just to show that you can target templates for a specific type of File; this renders PDF links --> <xsl:template match="node[@nodeTypeAlias = 'File'][data[@alias = 'umbracoExtension'] = 'pdf']"> <li> <a href="{data[@alias = 'umbracoFile']}" type="application/pdf" title="PDF file"> <xsl:value-of select="@nodeName" /> </a> </li> </xsl:template> </xsl:stylesheet>
    Feel free to ask about any of it - hope it gets you going.

    /Chriztian

  • Sam 184 posts 209 karma points
    Dec 09, 2010 @ 10:42
    Sam
    0

    Thanks Chriztian, that's very kind of you, will look over it more closely over the weekend :)

    One thing stands out to me:

         <xsl:template match="/">
                   
    <!-- This makes sure to only do something if there is a value in the property -->
                   
    <xsl:apply-templates select="$currentPage/data[@alias = 'rootDocFolder'][normalize-space()]" />
           
    </xsl:template

    Isn't the usual way to do a test to see if a property exists? Never seen 'normalize-space()' either.

    Thanks,

    Sam.

  • Chriztian Steinmeier 2799 posts 8789 karma points MVP 8x admin c-trib
    Dec 09, 2010 @ 11:06
    Chriztian Steinmeier
    0

    Hi Sam,

    That's the usual way of doing that in C#, VB etc. yes, but XSLT really works its magic, just by having a set of templates for how you want to render output for the various elements in the input XML. By then using the apply-templates instruction you tell the processor to grab the elements that match the selection you provide, and start finding the matching templates to do the rendering.

    We only want to render something when there's a value in the rootDocFolder property, and since we can just include that as a predicate on the selection, there's no need for a clumsy <xsl:if> </xsl:if> in which we would even have to repeat the XPath for the property...

    As for the normalize-space() function - it's just the best way to make sure that the property actually has a value. It will collapse all whitespace "streams" into a single space (or an empty string if no other characters were found) and thus, the predicate will return false for an empty rootDocFolder (or the instance of havinge pressed Save and currentPage doesn't even have a rootDocFolder property).

    Wow - long answer - hope you didn't fall asleep in the middle :-)

    /Chriztian

  • Sam 184 posts 209 karma points
    Dec 09, 2010 @ 13:57
    Sam
    0

    Hi Chriztian,

    Thanks for your help :)

    That makes sense to me and seems an excellent solution (from a logical point of view, I think I can see what you're getting at - my xslt is still pretty lame though). So what I think you're saying is that instead of using <if>s and <when>s surrounding everything and all over the place, you can simply use templates to do the job?

    I've read your article here:

    http://pimpmyxslt.com/articles/match-templates-intro/

    and I am 100% in favour for anything which is going to make code reusable in different circumstances. In theory then, could you make one giant macro with templates that could control every aspect of the whole site and insert that macro at each point where there is dynamic content, menus, gallery, downloads section etc etc?

    Sam.

  • Sam 184 posts 209 karma points
    Dec 12, 2010 @ 12:03
    Sam
    0

    Hi Chriztian,

    At the moment I have this:

    <h3>Docs</h3>

    <ul>

    <li>Doc1</li>

    <li>Doc2</li>

    <li>Form1</li>

    <li>Form2</li>

    </ul>

    when I actually want this:

    <h3>Docs</h3>

    <ul>

    <li>Doc1</li>

    <li>Doc2</li>

    </ul>

    <h3>Forms</h3>

    <ul>

    <li>Form1</li>

    <li>Form2</li>

    </ul>

    This is the structure in my media folder:

    --Docs

    ----Document1.doc

    ----Document2.pdf

    --Forms

    ----Form1.doc

    ----Form2.pdf

    Would I have to add another template like the following in order to get the second list?

            <!-- Template for the property -->
            <xsl:template match="data[@alias = 'rootDocFolder-downloads']">
                    <!-- Fetch the media XML and start the processing -->
                    <xsl:variable name="mediaFolder" select="umbraco.library:GetMedia(., true())" />
                    <!-- ...but only if we did not get an error back -->
                    <xsl:apply-templates select="$mediaFolder[not(error)]" />
            </xsl:template>

            <xsl:template match="data[@alias = 'rootDocFolder-forms']">
                    <!-- Fetch the media XML and start the processing -->
                    <xsl:variable name="mediaFolder" select="umbraco.library:GetMedia(., true())" />
                    <!-- ...but only if we did not get an error back -->
                    <xsl:apply-templates select="$mediaFolder[not(error)]" />
            </xsl:template>

    I'm not sure how the templates method can go through the tree structure like a <for-each> can.

    Thanks if you can shed some more light, I think this template method will come in very useful in my future work. :)

    Sam.

  • Chriztian Steinmeier 2799 posts 8789 karma points MVP 8x admin c-trib
    Dec 12, 2010 @ 12:48
    Chriztian Steinmeier
    1

    Hi Sam,

    Actually, if you change these lines in the template for the 'Folder' items:

    <!-- Render all Files below this, regardless of level  -->
    <xsl:apply-templates select="descendant::node[@nodeTypeAlias = 'File']" />
    

    into this:

    <!-- Render all Files or (sub-)Folders in this folder -->
    <xsl:apply-templates select="node[@nodeTypeAlias = 'File'] | node[@nodeTypeAlias = 'Folder']" />

    You'll be pretty close.

    The original deliberately skipped the subfolders by just selecting File nodes at all levels below the original folder.

    The new one takes advantage of template matching by just asking the processor to process either File or Folder elements within a Folder - which will automatically run through any number of subfolders you decide to create.  

    /Chriztian

  • Sam 184 posts 209 karma points
    Dec 12, 2010 @ 13:08
    Sam
    0

    Thanks Chriztian,

    That is pretty close, this outputs:

    <h3>docsh3>

    <ul>

    <h3>Folder1h3>

    <ul>

    <li><a href="#">doc1.pdfa>li>

    ul>

    <h3>Folder2h3>

    <ul>

    <li><a href="#">doc1.pdfa>li>

    ul>

    <h3>Folder3h3>

    <ul>

    <li><a href="#">doc1.doca>li>

    ul>

    ul>

    Sam.

  • Sam 184 posts 209 karma points
    Dec 12, 2010 @ 13:19
    Sam
    0

    Thanks so much Chriztian, this now works as it should, complete code (edited - needed to change it a bit)

    <?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="xml" indent="yes" omit-xml-declaration="yes" />

    <xsl:param name="currentPage" />

    <xsl:template match="/">
    <!-- This makes sure to only do something if there is a value in the property -->
    <xsl:apply-templates select="$currentPage/data[@alias = 'rootDocFolder'][normalize-space()]" />
    </xsl:template>

    <!-- Template for the property -->
    <xsl:template match="data[@alias = 'rootDocFolder']">
    <!-- Fetch the media XML and start the processing -->
    <xsl:variable name="mediaFolder" select="umbraco.library:GetMedia(., true())" />
    <!-- ...but only if we did not get an error back -->
    <xsl:apply-templates select="$mediaFolder[not(error)]" />
    </xsl:template>

    <!-- Template for the root folder -->
    <xsl:template match="node[@nodeTypeAlias = 'Folder']/node">
    <h3><xsl:value-of select="@nodeName" /></h3>
    <ul>
    <!-- Render all Files or (sub-)Folders in this folder -->
    <xsl:apply-templates select="node[@nodeTypeAlias = 'File'] | node[@nodeTypeAlias = 'Folder']" />
    </ul>
    </xsl:template>

    <!-- Template for a file -->
    <xsl:template match="node[@nodeTypeAlias = 'File']">
    <li>

    <a href="{data[@alias = 'umbracoFile']}">
    <xsl:value-of select="@nodeName" />
    </a>

    <xsl:choose>
    <xsl:when test="string(data [@alias='issueDate']) != '' ">
    <xsl:text>(</xsl:text><xsl:value-of select="umbraco.library:FormatDateTime(./data [@alias = 'issueDate'],'d MMM yyyy')" disable-output-escaping="yes"/><xsl:text>)</xsl:text>
    </xsl:when>

    <xsl:otherwise>
    </xsl:otherwise>

    </xsl:choose>

    </li>

    </xsl:template>

    </xsl:stylesheet>

    This template method is very interesting, I'm pretty sure I will be using a lot of this in the future :)

    I have added a few bits, and need to convert the rest into templates, also to sort the list?

    Sam.

  • Sam 184 posts 209 karma points
    Dec 12, 2010 @ 14:52
    Sam
    0

    Any chance you could explain this bit to me:

    <xsl:variable name="mediaFolder" select="umbraco.library:GetMedia(., true())" />

    The dot after GetMedia, and the () after true? Basically, what does this bit mean? Get ALL media? Diff between true or false, why would someone put the statement if it was going to be false?

    Thanks,

    Sam.

     

  • Chriztian Steinmeier 2799 posts 8789 karma points MVP 8x admin c-trib
    Dec 12, 2010 @ 20:54
    Chriztian Steinmeier
    1

     

    Hi Sam,

    The dot just refers to what's called the the "current node" in XSLT - in this case, the <data alias="rootDocFolder"> element, since we're inside that template.

    The second argument of GetMedia() is called booleanDeep and it specifies whether you want the complete structure below the node you've specified or just that single node's XML. Usually when specifying the ID of a folder you'd want its contents, and thus, set the second argument to boolean true. Otherwise you're typically asking for just a file and therefore the boolean false is better.

    The parentheses are a little harder to explain - but really it's how you express a boolean in XSLT, and the important thing to understand here is, that at the time of specifying the arguments to the GetMedia() function, you're still in XPath/XSLT land and you must use the correct syntax to supply the arguments.

    So we provide XPaths to the values needed, e.g. "." to tell the processor where to find the ID, and true() or false() to provide a boolean value for the second argument. You can also use 1 (one) or 0 (zero), as they will convert just fine to their boolean counterparts.

    You should not, however, use any of these: false, true, 'false', 'true' - because they won't work as expected. The first two - false + true - will both result in a boolean false(); The last two - 'false' + 'true' will both result in a boolean true()... Why? The first two are actually "location steps", which means the processor will look for a value in a childnode called <false> (or <true>) which doesn't exist; The others are strings and any non-empty string will convert to boolean true.

    Using those (and a lot of examples do, I know) will usually work fine, right until the day you need to flip them and use the other value, and you end up cursing XSLT to hell for being so ****** [censored] about booleans, because nothing changes :-) 

    Phew, hope that was kind of the answer you were after...

    /Chriztian

     

  • Sam 184 posts 209 karma points
    Dec 13, 2010 @ 21:43
    Sam
    0

    Thanks Chriztian,

    Your help is very useful and very much appreciated. I am very interested in the templates method as it seems very sensible. I don't mean to be a drag on your time but does it also work for replacing <choose>, <when> and <otherwise> like within the following <for-each> from my website? Would the followng code (which works - isn't that the desired outcome? lol maybe that's forgotten sometimes...) be better keeping as is, or adopting the templates method so it could possible be used in another situation. Here it is:

     

    <xsl:template match="/">

    <div id="list-wrapper">
    <ul>
    <xsl:for-each select="$currentPage/descendant-or-self::node [@nodeTypeAlias = 'PortfolioItem']">
    <xsl:sort select="./@createDate" order="descending" />
    <li>

    <xsl:variable name="webPreviewImage" select="./data [@alias = 'WebsitePreviewImage1']"/>

    <xsl:choose>
    <xsl:when test="string($webPreviewImage) != ''">
    <xsl:variable name="media" select="umbraco.library:GetMedia($webPreviewImage, false)" />
    <a href="{umbraco.library:NiceUrl(./@id)}"><img alt="{$media/@nodeName}" src="{$media/data [@alias = 'umbracoFile']}" width="250" /></a>
    </xsl:when>
    <xsl:otherwise>
    <a href="{umbraco.library:NiceUrl(./@id)}"><img src="/images/rollback.png" alt="No preview available" width="250" /></a>
    </xsl:otherwise>
    </xsl:choose>

    <p><a href="{umbraco.library:NiceUrl(./@id)}"><xsl:value-of select="@nodeName"/></a></p>

    </li>
    </xsl:for-each>
    </ul>
    </div>

    </xsl:template>

    This displays an image in the list with the nodename underneath, the image I choose in the content section (alias WebsitePreviewImage1), otherwise if image not present, show rollback image. How would this <for-each> be split up into templates?

    If it's a pain in the ****, don't worry about it lol :) thanks.

    Sam.

  • Chriztian Steinmeier 2799 posts 8789 karma points MVP 8x admin c-trib
    Dec 13, 2010 @ 22:19
    Chriztian Steinmeier
    0

    Hi again,

    I really like your attitude Sam; You really want to know why, and not just get the working code, so keep that up :-) And be sure to post new questions in separate posts, so that people like yourself coming for answers can find them. It's easier when there's only one question + answer per post.

    Regarding replacing choose, when, otherwise and if with templates - You can very often do exactly that (I even spoke about that just about a month ago, at the Danish Umbraco Festival - slides in danish here: http://pimpmyxslt.com/presentations/ )

    I'd refactor your chunk into the following, and it's really just about finding those bits that should be self-contained, extracting them into their own template(s).

    <xsl:template match="/">
        <div id="list-wrapper">
            <ul>
                <xsl:apply-templates select="$currentPage/descendant-or-self::node[@nodeTypeAlias = 'PortfolioItem']">
                    <xsl:sort select="@createDate" data-type="text" order="descending" />
                </xsl:apply-templates>
            </ul>
        </div>
    </xsl:template>
    
    <xsl:template match="node[@nodeTypeAlias = 'PortfolioItem']">
        <li>
            <a href="{umbraco.library:NiceUrl(@id)}">
                <xsl:apply-templates select="data[@alias = 'WebsitePreviewImage1']" />
            </a>
            <p>
                <a href="{umbraco.library:NiceUrl(@id)}">
                    <xsl:value-of select="@nodeName" />
                </a>
            </p>
        </li>
    </xsl:template>
    
    <!-- Template for the WebsitePreviewImage1 property -->
    <xsl:template match="data[@alias = 'WebsitePreviewImage1']">
        <xsl:variable name="media" select="umbraco.library:GetMedia(., false())" />
        <img alt="{$media/@nodeName}" src="{$media/data[@alias = 'umbracoFile']}" width="250" />
    </xsl:template>
    
    <!-- Template for an empty WebsitePreviewImage1 property -->
    <xsl:template match="data[@alias = 'WebsitePreviewImage1'][not(normalize-space())]">
        <img src="/images/rollback.png" alt="No preview available" width="250" />
    </xsl:template>
    

    As you can see, I chose to have the WebsitePreviewImage1 templates handle the case of no data, leaving the main code (in the PortfolioItem template) be happily ignorant to that problem - it just knows to apply the WebsitePreviewImage1 template.

    Oh and and Sam: Do try to get your hands on a 4.5 installation soon - the new XML Schema is much better suited to using this template approach - i.e. the template "signatures" above would look like this:

    <xsl:template match="/">
    <xsl:template match="PortfolioItem">
    <xsl:template match="WebsitePreviewImage1">
    <xsl:template match="WebsitePreviewImage1[not(normalize-space())]">
    
    

    Prettier, huh? Same goes for picking the properties...

    /Chriztian

     

  • Sam 184 posts 209 karma points
    Dec 14, 2010 @ 09:38
    Sam
    0

    Thanks Chriztian,

    Once again, a great response! I will study this code more closely later when I get back from work, I do two other jobs during the week so learning this xslt is taking longer than I would have liked! :)

    ps the 4.5 code does look hell of a lot neater I agree. I will be ringing my hosting company this week to see whether or not they are running .net 4 yet, hence me still on 4.0.4.2.

    Will post back once I've been through the code and tried to work out what everything does :)

    Sam.

  • Sam 184 posts 209 karma points
    Dec 15, 2010 @ 20:30
    Sam
    0

    Hi Chriztian,

    I'm on v4.5.2 now, installed today, so I will give it a go translating the xslt into the new schema.

    Sam.

  • Sam 184 posts 209 karma points
    Dec 16, 2010 @ 17:00
    Sam
    0

    Hi Chriztian,

    I promise, last question!

    How would I get the sorting sorted via the templates method? I can do this in a for-each with this:

     

       <xsl:sort select="umbraco.library:FormatDateTime(data [@alias='issueDate'], 'yyyyMMdd')" data-type="number" order="ascending" />
    I can't make this a child template. I have seen your other post about putting the sorting into a choose statement instead but I'm unsure of how to mix this together. This alias (issueDate) is on the properties of the file itself in the media section, not in the content section. So do I then need to specify that this a media node? My whole xslt file to list downloads now looks like this:
    <?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="xml" indent="yes" omit-xml-declaration="yes" />
    
            <xsl:param name="currentPage" />
    
            <xsl:template match="/">
                    <!-- This makes sure to only do something if there is a value in the property -->
                    <xsl:apply-templates select="$currentPage/data[@alias = 'rootDocFolder'][normalize-space()]" />
            </xsl:template>
    
            <!-- Template for the property -->
            <xsl:template match="data[@alias = 'rootDocFolder']">
                    <!-- Fetch the media XML and start the processing -->
                    <xsl:variable name="mediaFolder" select="umbraco.library:GetMedia(., true())" />
                    <!-- ...but only if we did not get an error back -->
                    <xsl:apply-templates select="$mediaFolder[not(error)]" />
            </xsl:template>
    
            <!-- Template for the root folder -->
            <xsl:template match="node[@nodeTypeAlias = 'Folder']/node">
                    <h3><xsl:value-of select="@nodeName" /></h3>
                    <ul>
    <!-- Render all Files or (sub-)Folders in this folder -->
    <xsl:apply-templates select="node[@nodeTypeAlias = 'File'] | node[@nodeTypeAlias = 'Folder']" />
                    </ul>
            </xsl:template>
    
            <!-- Template for a file -->
            <xsl:template match="node[@nodeTypeAlias = 'File']">                <li>
                            <a href="{data[@alias = 'umbracoFile']}">
                                    <xsl:value-of select="@nodeName" />
                            </a>
    <xsl:choose>
    <xsl:when test="string(data [@alias='issueDate']) != '' ">
    <xsl:text> (</xsl:text><xsl:value-of select="umbraco.library:FormatDateTime(./data [@alias = 'issueDate'],'d MMM yyyy')" disable-output-escaping="yes"/> - <xsl:value-of select="string(data [@alias='umbracoExtension'])"/> format<xsl:text>)</xsl:text>
    </xsl:when>
        <xsl:otherwise>
        </xsl:otherwise>
    </xsl:choose>
                    </li>
            </xsl:template>
    
    </xsl:stylesheet>
    Adding /node to this line sorted the problem of showing the root folder, I only wanted sub-folders:
            <xsl:template match="node[@nodeTypeAlias = 'Folder']/node">
    And also, yes I know I have put a choose when test when test choose otherwise blah blah blah in there just to get it to work... :(
    In a nutshell, I am trying to get the above sorted by issueDate in a templates only xslt. Any help would be very much appreciated.
    Thanks :)
    Sam.

     

  • Chriztian Steinmeier 2799 posts 8789 karma points MVP 8x admin c-trib
    Dec 16, 2010 @ 18:06
    Chriztian Steinmeier
    0

    Hi Sam,

    Just grab the nearest Stanley knife and split the <xsl:apply-templates /> instruction open - then put the <xsl:sort /> instruction inside:

    <xsl:apply-templates select="node[@nodeTypeAlias = 'File'] | node[@nodeTypeAlias = 'Folder']">
        <xsl:sort select="data[@alias = 'issueDate']" data-type="text" order="descending" />
    </xsl:apply-templates>
    
    Note that you don't need to call the FormatDateTime() function (which you were doing for every item, mind you) - XML dates are perfectly sortable as text. (Free bonus)

    /Chriztian

  • Sam 184 posts 209 karma points
    Dec 16, 2010 @ 19:59
    Sam
    0

    Thanks Chriztian,

    That's great! :)

    Sam.

Please Sign in or register to post replies

Write your reply to:

Draft