Copied to clipboard

Flag this post as spam?

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


  • Pete Brown 47 posts 77 karma points
    Feb 25, 2010 @ 01:09
    Pete Brown
    0

    Better way to get the 8 or so most recent blog posts?

    Perf on this stinks. Looking at it, no wonder why. The xslt iterates through all the blog posts (I currently have over 500) just to get the most recent 8. Is there a better way to do this?

    <?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:tagsLib="urn:tagsLib"
     xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings"
     exclude-result-prefixes="msxml umbraco.library tagsLib Exslt.ExsltStrings">

    <xsl:output method="html" omit-xml-declaration="yes"/>
    <xsl:param name="currentPage"/>
    <xsl:variable name="numberOfPosts" select="8"/>

    <!-- This is super brittle. I hate having the ID in here. Need to find another way. Get the ID from the Blog node properties on the content page -->
    <xsl:variable name="blogPosts" select="umbraco.library:GetXmlNodeById(1163)/descendant-or-self::node [@nodeTypeAlias = 'Blog']//node [@nodeTypeAlias = 'BlogPost']" />
    <xsl:template match="/">
    <ul class="blog-post-excerpt">
    <xsl:for-each select="$blogPosts">
    <xsl:sort select="@createDate" order="descending" />
    <xsl:if test="position() &lt;= $numberOfPosts">
     <xsl:call-template name="showpost">
      <xsl:with-param name="post" select="."/>
     </xsl:call-template>
    </xsl:if>
    </xsl:for-each>
    </ul>
    </xsl:template>
    <xsl:template name="showpost">
     <xsl:param name="post"/>
     <li class="blog-post-excerpt">
      <h4><a href="{umbraco.library:NiceUrl($post/@id)}"><xsl:value-of select="$post/@nodeName"/></a></h4>
      <p> <!-- class="entry" -->
       <xsl:value-of select="umbraco.library:TruncateString(umbraco.library:StripHtml($post/data [@alias = 'bodyText']), 400, '...')" disable-output-escaping="yes"/>
      </p>
     </li>
    </xsl:template>
    </xsl:stylesheet>

     

  • Paul Blair 466 posts 731 karma points
    Feb 25, 2010 @ 02:49
    Paul Blair
    0

    If you know you are doing more than 8 posts per month (or week/day etc) then you could further reduce your blogposts variable by only getting posts for the last month by createdate.

    It's not the most subtle solution but I don't believe there is any way to break out of the for-each loop like you would in c#.

    Cheers

    Paul

  • Pete Brown 47 posts 77 karma points
    Feb 25, 2010 @ 03:48
    Pete Brown
    0

    Thanks.

    That works, but breaks down when you're near the start of the month (you'd need to add in the previous month too), and then the odd slow months etc. Good idea though.

    I'm thinking I should just go write a utility lib function in C# and have it do some sql to handle this.

    Pete

  • Pete Brown 47 posts 77 karma points
    Feb 25, 2010 @ 05:59
    Pete Brown
    0

    This site was down, so I posted my solution to the codeplex forums. Interested in validation on the sql as well as if there's a nice generic way to get the right document type (or if hard-coding it is correct). Here's the post:

    http://blog4umbraco.codeplex.com/Thread/View.aspx?ThreadId=178688

    And here's the code I added to BlogLibrary.cs

    public static XPathNodeIterator GetLatestPosts(int count)
    {
        // get the blog doctype. Hard coding this is probably not a good idea, but will work for my blog
        umbraco.cms.businesslogic.web.DocumentType docType = umbraco.cms.businesslogic.web.DocumentType.GetByAlias("BlogPost");
        if (docType == null) throw new Exception("Unable to find document type for BlogPost alias");
        ISqlHelper SqlHelper = DataLayerHelper.CreateSqlHelper(umbraco.GlobalSettings.DbDSN);

        string sql = "SELECT TOP " + count + "x.[xml] xml " +
                    "FROM cmsContentXml x JOIN cmsDocument d ON x.nodeId = d.nodeId " +
                        "JOIN cmsContent c ON x.nodeId = c.nodeId " +
                        "JOIN umbracoNode n ON x.nodeId = n.id " +
                        "WHERE d.published = 1 AND " +
                            "c.contentType = @contentTypeId " +
                            "ORDER BY n.createDate desc";

        IRecordsReader rr = SqlHelper.ExecuteReader(
            sql,
             SqlHelper.CreateParameter("@contentTypeId", docType.Id));

        XmlDocument xd = new XmlDocument();
        XmlNode x = umbraco.xmlHelper.addTextNode(xd, "nodes", "");
        while (rr.Read())
        {
            x.AppendChild(umbraco.xmlHelper.ImportXmlNodeFromText(rr.GetString("xml"), ref xd));
        }
        xd.AppendChild(x);
        return xd.CreateNavigator().Select(".");
    }

    It appears to work fine (and is pretty fast).

    Pete

  • Morten Bock 1867 posts 2140 karma points MVP 2x admin c-trib
    Feb 25, 2010 @ 09:18
    Morten Bock
    0

    I am very surprised that it would be faster to go to the db instead of working directly with xslt since alle the xml is in memory.

    What happens if you change this

     

    <xsl:variable name="blogPosts" select="umbraco.library:GetXmlNodeById(1163)/descendant-or-self::node [@nodeTypeAlias = 'Blog']//node [@nodeTypeAlias = 'BlogPost']" />

    with this

     

    <xsl:variable name="blogPosts" select="$currentPage/ancestor-or-self::node[@level = '1']/descendant-or-self::node [@nodeTypeAlias = 'Blog']//node [@nodeTypeAlias = 'BlogPost']" />

    You may need to cange the @level = '1' to match your needs.

    This would avoid the GetXmlNodeById() call.

     

     

  • dandrayne 1138 posts 2262 karma points
    Feb 25, 2010 @ 10:21
    dandrayne
    1

    Also, you can move the position() inside the initial selector, instead of testing within the loop, e.g.

    <xsl:variable name="blogPosts" select="$currentPage/ancestor-or-self::node[@level = '1']/descendant-or-self::node [@nodeTypeAlias = 'Blog']//node [@nodeTypeAlias = 'BlogPost'][position() &lt;= 8]" />

    Not sure how much of a performance boost that may be tbh...  

    With a macro like this, you could also get away with caching it pretty aggressively.  That should help massively with any speed problems.

    Dan

  • Peter Dijksterhuis 1442 posts 1722 karma points
    Feb 25, 2010 @ 10:27
    Peter Dijksterhuis
    0

    As addition to this, you can easily extend your variable to check against the createDate as well. You can either use the Exslt or the date functions in the umbraco.library. This will help reduce the number of nodes you get in your variable.

    For example:

    Add this to the top of the xslt: xmlns:date="urn:Exslt.ExsltDatesAndTimes"

    Then:

    <xsl:variable name="startdate" select="date:add(date:date(),'-P2M')"/>
    <xsl:variable name="blogPosts" select="$currentPage/ancestor-or-self::node[@level = '1']/descendant-or-self::node [@nodeTypeAlias = 'Blog']//node [@nodeTypeAlias = 'BlogPost' and umbraco.library:DateGreaterThanOrEqual(@createDate, $startdate)]" />

    the variable startdate gets filled with (today minus 2 months). You can adjust the -P2M parameter to suit your needs if you need a smaller or wider timeframe 

    HTH,

    Peter

  • Pete Brown 47 posts 77 karma points
    Feb 25, 2010 @ 15:48
    Pete Brown
    0

    Wait, so, when umbraco starts up, it loads all the documents into memory? That explains why I have such a long startup time -- and my site is only half-populated.

    Has anyone tried this with hundreds or thousands of documents?

    @Dan how can I control caching for this xslt?

    Pete

  • dandrayne 1138 posts 2262 karma points
    Feb 25, 2010 @ 16:01
    dandrayne
    0

    In the devloper section, open the "macros" tree and select the macro that relates to this piece of xslt.  It is from here that you can control cache options such as "cache time", "cache per page" etc.  With a piece of xslt like this you could afford a long cache time.

    As for large sites, conde naste use umbraco for some fairly large sites (wired.co.uk being the most notable). The biggest we've used has well over 10000 nodes with no real slowdown.  Once the xml is loaded into memory it should be fairly snappy.

    Dan

  • Pete Brown 47 posts 77 karma points
    Feb 25, 2010 @ 17:35
    Pete Brown
    0

    @dan

    excellent, thanks.

    Pete

  • Pete Brown 47 posts 77 karma points
    Feb 25, 2010 @ 18:46
    Pete Brown
    0

    @Morten

    I can't for the life of me figure out how to get the Blog folder that sits off the root. The xpath doesn't work because the Blog folder is not a child of the home page, it's a sibling under Content. Here's how my tree looks:

    Content

    |- Home (the macro is run from here - the xpath returns this node)

    |- Blog (can't figure out the xpath to return this node)

    |--- 2010

    |-----2

    |-------24

    |--------- Blog Post 1

    |--------- Blog Post 2

    |-------25

    |--------- Blog Post 3

    |--------- Blog Post 4

    |--------- Blog Post 5

     

    I don't count xpath/xslt among my strong skills, so I may have missed something obvious, but is there a way to get to a sibling og the home page to find the Blogs folder? Searching on the forums here and in general xslt sites isn't yielding anything useful.

    Thanks.

    Pete

  • Pete Brown 47 posts 77 karma points
    Feb 25, 2010 @ 18:59
    Pete Brown
    0

    Looking at other posts here, I think that's er... the root of my problem. I'm going to change the structure of my site a bit so I really end up with a single "site" root with everything under it.

    Send ninjas to my house if I'm going about this the wrong way :) 

    Pete

  • Peter Dijksterhuis 1442 posts 1722 karma points
    Feb 25, 2010 @ 19:11
    Peter Dijksterhuis
    0

    No need to. Have you checked my post?

    Here's a full working example:

    The variable holds todays-date minus 2 months (if you have few posts per months, you could extend this, change the -P2M into -P3M for 3 months)

     

    <?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:tagsLib="urn:tagsLib"
     xmlns:date="urn:Exslt.ExsltDatesAndTimes"
     xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings"
     exclude-result-prefixes="msxml umbraco.library tagsLib Exslt.ExsltStrings date">

    <xsl:output method="html" omit-xml-declaration="yes"/>
    <xsl:param name="currentPage"/>
    <xsl:variable name="numberOfPosts" select="8"/>

    <xsl:variable name="startdate" select="date:add(date:date(),'-P2M')"/>

    <xsl:variable name="blogPosts" select="$currentPage/ancestor-or-self::node//node [@nodeTypeAlias = 'Blog']//node [@nodeTypeAlias = 'BlogPost' and umbraco.library:DateGreaterThanOrEqual(@createDate, $startdate)]" />

    <xsl:template match="/">
    <ul class="blog-post-excerpt">
    <xsl:for-each select="$blogPosts">
    <xsl:sort select="@createDate" order="descending" />
    <xsl:if test="position() &lt;= $numberOfPosts">
     <xsl:call-template name="showpost">
      <xsl:with-param name="post" select="."/>
     </xsl:call-template>
    </xsl:if>
    </xsl:for-each>
    </ul>
    </xsl:template>
    <xsl:template name="showpost">
     <xsl:param name="post"/>
     <li class="blog-post-excerpt">
      <h4><a href="{umbraco.library:NiceUrl($post/@id)}"><xsl:value-of select="$post/@nodeName"/></a></h4>
      <p> <!-- class="entry" -->
       <xsl:value-of select="umbraco.library:TruncateString(umbraco.library:StripHtml($post/data [@alias = 'bodyText']), 400, '...')" disable-output-escaping="yes"/>
      </p>
     </li>
    </xsl:template>
    </xsl:stylesheet>

    HTH,

    Peter

  • Peter Dijksterhuis 1442 posts 1722 karma points
    Feb 25, 2010 @ 19:12
    Peter Dijksterhuis
    0

    Also, you might want to read this page about xpaths: 

    http://our.umbraco.org/wiki/reference/xslt/xpath-axes-and-their-shortcuts

    I saw a different one yesterday, but was unable to find it right now :(

  • Pete Brown 47 posts 77 karma points
    Feb 25, 2010 @ 19:31
    Pete Brown
    0

    Moving my site to a better structure (with the root folder being the "home page") has made this and some other things just work.

    I'm still not satisfied with perf on generating that list of blog posts, but I'll work on it some more, plus add in caching once perf is acceptable.

    Pete

  • Pete Brown 47 posts 77 karma points
    Feb 25, 2010 @ 19:33
    Pete Brown
    0

    @peter

    That just didn't work, as the home page was not the parent of the blog folder (the query always came back empty). I've changed the structure of the site so now it is the root and it's working.

    Pete

  • Peter Dijksterhuis 1442 posts 1722 karma points
    Feb 25, 2010 @ 19:42
    Peter Dijksterhuis
    0

    Hm, I created a similar setup to yours:

    Content

    -Home

    -Blog

    Worked fine on my site, it showed the blog-items on the homepage.

    Glad you have it working though.

  • Morten Bock 1867 posts 2140 karma points MVP 2x admin c-trib
    Feb 25, 2010 @ 23:07
    Morten Bock
    0

    Just for reference, it is possible to get to the root by doing this:

    <xsl:for-each select="$currentPage/ancestor-or-self::root/node[@nodeName = 'blog']/and so on....">

    The xml we are parsing looks like this:

    <root id="-1">
      <node nodeName="Home" .....>
        <node nodeName="Blog" .....>
    </root>

    And you can always see that xml in your /data/umbraco.config file

Please Sign in or register to post replies

Write your reply to:

Draft