Link to Detail XSLT

The Functions

We have three functions here. All the functions are XHTML-based. The NewsArchive and NewsLatest functions are very similar. NewsLatest has a little less functionality than NewsArchive.

So we’ll focus on NewsArchive and NewsDetails. NewsDetails is the most complex but also the most interesting one to learn from.

NewsArchive

This function lists the news articles grouped by category. You can also specify which category you would like to see.

The function needs to return links to the “news details” page so here we have an input parameter called NewsDetailsPage which holds that URL.

Figure 8: The NewsDetailsPage input parameter in NewsArchive

This function calls three other functions to:

  • Get the news data (using the generated Get<Type>XML function)
  • Get category data (using the generated Get<Type>XML function)
  • Get the query string parameter to specify a category (optional)

It is pretty straightforward with the C1 CMS generated functions to get your data in XML.

Figure 9: Getting fields in XML

Figure 10: Sorting items by date

Figure 11: Getting the category data and fields

Figure 12: Getting the “?Category=Sport” value from the query string

The logic is implemented in XSLT.

Note: If you have a lot of categories and you filter them, it is better to use the filter on the GetItemXml function.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="xsl in"
        xmlns:in="http://www.composite.net/ns/transformation/input/1.0"
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:msxsl="urn:schemas-microsoft-com:xslt"
        xmlns:user="urn:my-scripts"
        >
  <msxsl:script language="C#" implements-prefix="user">
    <![CDATA[
            public string FormatDate(DateTime Date)
            {
                  return Date.ToString("dddd, dd MMMM yyyy");
            }
      ]]>
  </msxsl:script>
  <xsl:param name="items" select="/in:inputs/in:result[@name='GetItemXml']" />
  <xsl:param name="categories" select="/in:inputs/in:result[@name='GetCategoryXml']" />
  <xsl:param name="category" select="/in:inputs/in:result[@name='Category']" />
  <xsl:template match="/">
    <html>
      <head></head>
      <body>
        <div class="NewsLatest">
          <ul>
            <xsl:apply-templates mode="NewsCategory" select="$categories/*[$category = '' or $category = @Name]">
            </xsl:apply-templates>
          </ul>
        </div>
      </body>
    </html>
  </xsl:template>
  <xsl:template mode="NewsCategory" match="*">
    <xsl:variable name="id" select="@Id" />
    <li>
      <a href="?Category={@Name}" class="NewsCategory" ><xsl:value-of select="@Name" /></a>
      <ul>
        <xsl:apply-templates mode="NewsItem" select="$items/*[@Category.Id = $id]">
          <xsl:sort select="@Date" order="ascending"/>
        </xsl:apply-templates>
      </ul>
    </li>
  </xsl:template>
  <xsl:template mode="NewsItem" match="*">
    <li>
      <xsl:if test="position() = 1" >
        <xsl:attribute name="class" >First</xsl:attribute>
      </xsl:if>
      <xsl:if test="position() = last()" >
        <xsl:attribute name="class" >Last</xsl:attribute>
      </xsl:if>
      <span class="NewsDate">
<xsl:value-of select="user:FormatDate(@Date)"/>
      </span><br/>
      <span class="NewsTitle">
        <a href="">
          <xsl:value-of select="@Title"/>
        </a>
      </span>
    </li>
  </xsl:template>
</xsl:stylesheet>

Listing 4: The NewsArchive template code

Figure 13: Previewing the function

NewsDetails

This is one of the most interesting parts. We will need filtering and do that using CMS functions. And no programming at all!

We want to get a news article so that we use the Get<Type>XML function to get the XML for our news article and specify which fields we want to retrieve to build our news details:

Figure 14: Selected fields

But we don’t want all articles. We only want a specific item, which we specify by using the query string’s news ID. So we will filter to only get that specific News Item element instead of getting all of them

The filter expects the expression function <Func<News Item,Boolean>>.

Basically this expression walks along all the News Item items, evaluates the News Item data and returns a Boolean to say if it passes the filter’s True / False.

We can build such an expression using an input parameter.

After we have built the expression, we need to indicate that we want to use that input parameter by specifying its name. We will call it “Filter” (see the text below the following screenshot). When you set up the “Filter” parameter (see below), you will be able to switch to the Input Parameter type and select the input parameter:

Figure 15: Naming the input parameter

Building a filter expression using an input parameter

So now we need to make an expression function <func<NewsItem,Boolean>> (also called a predicate function in .NET), which will be used as our filter in the GetItemXml function.

We need to give it a name. (In the previous section, we decided to call this input parameter “Filter”).

In the parameter type, select the Expression<Func<News Item,Boolean>>.

Figure 16: The input parameter expression

 

We will filter on a specific ID using a GUID.

We make a complex function call and take the GUID from the query string’s NewsID:

  1. For the default value, call News Item’s GetPredicatesFilter function.
  2. For its IdFilter parameter, call Composite.Utils.Predicates.GuidEquals.
  3. For it’s the Value to compare with parameter, call Composite.Web.Request.QueryStringGuidValue and set its Parameter name to “NewsID”.

Figure 17: Editing the default value to filter on an ID and using NewsID from the query string

So this is where you could decide not to use the GUID but rather filter on the title (or an extra “no spaces” title field) and get a string from the query string.

That was almost it. We still need the XSLT to create our detailed page out of the XML.

Figure 18: XSLT extension

In our XSLT, we will use an XSLT extension: xmlns:mp="#MarkupParserExtensions" - to parse the HTML from the news article body:

<div class="NewsDescription">
  <xsl:copy-of select="mp:ParseXhtmlBodyFragment(@Description)"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
</div>

Listing 5: Using the ParseXhtmlBodyFragment markup parser extension

We will also use XSLT to render the news details:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="xsl in"
        xmlns:in="http://www.composite.net/ns/transformation/input/1.0"
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:msxsl="urn:schemas-microsoft-com:xslt"
        xmlns:user="urn:my-scripts"
        xmlns:mp="#MarkupParserExtensions"
        >
  <msxsl:script language="C#" implements-prefix="user">
    <![CDATA[
            public string FormatDate(DateTime Date)
            {                  
               return Date.ToString("dddd, dd MMMM yyyy");
            }
      ]]>
  </msxsl:script>
        <xsl:template match="/">
                <html>
                        <head>
                        </head>
                        <body>
                          <div class="NewsDetails">
                            <xsl:apply-templates mode="NewsItem" select="/in:inputs/in:result[@name='GetItemXml']/*"></xsl:apply-templates>
                          </div>
                        </body>
                </html>
        </xsl:template>
        <xsl:template mode="NewsItem" match="*">
                <div class="NewsItem">
                        <div class="NewsDate">
                                <xsl:value-of select="user:FormatDate(@Date)"/>
                        </div>
                        <div class="NewsTitle">
                                <xsl:value-of select="@Title"/>
                        </div>
                        <div class="NewsDescription">
                                <xsl:copy-of select="mp:ParseXhtmlBodyFragment(@Description)"/>
                        </div>
                </div>
        </xsl:template>
</xsl:stylesheet>

Listing 6: NewsDetails

Note: You might want to specify a GUID as a test value so that you could preview your function. In our case, there is nothing to preview since our current filter returns no valid element.

Figure 19: Previewing the function

NewsLatest

This function is similar to the NewsArchive function, except that it shows a fixed maximum number of articles per category.

Figure 20: NewLatest with the PageSize parameter set to 1 (1 news in each category)

Figure 21: The PageSize parameter and parameters for hyperlinks

Figure 22: Nothing really new here

It is the XSLT that takes care of the counting.

Note: There is no filter in here. If you have loads of news items, this might give the performance you want. In such a case, it would be better to use a filter on the GetItemXml to limit the total number of XML items to work with.

The problem is that if you want 5 items per category and you have 3 categories, you don’t know how many news items you need to retrieve to fill each category up to 5 items.

You could filter on a date. You could use the page size (for example, 25, and hopefully you get enough variation in category news items in there). You could also do it perfectly and filter on the Category and PageSize with your amount but then you will have to add a function for each list.

You should work it out. Only keep in mind that this sample is not what you want to use on something like 1000 news items without changes.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="xsl in"
        xmlns:in="http://www.composite.net/ns/transformation/input/1.0"
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:msxsl="urn:schemas-microsoft-com:xslt"
        xmlns:user="urn:my-scripts"
        >
  <msxsl:script language="C#" implements-prefix="user">
    <![CDATA[
            public string FormatDate(DateTime Date)
            {
                  return Date.ToString("dddd, dd MMMM yyyy");
            }
      ]]>
  </msxsl:script>
  <xsl:param name="items" select="/in:inputs/in:result[@name='GetItemXml']" />
  <xsl:param name="categories" select="/in:inputs/in:result[@name='GetCategoryXml']" />
  <xsl:param name="pagesize" select="/in:inputs/in:param[@name='PageSize']" />  <xsl:template match="/">
    <html>
      <head>
        <!-- markup placed here will be shown in the head section of the rendered page -->
      </head>
      <body>
        <div class="NewsLatest">
          <ul>
            <xsl:apply-templates mode="NewsCategory" select="$categories/*">
            </xsl:apply-templates>
          </ul>
          <a href="/page({/in:inputs/in:param[@name='NewsArchivePage']})">
            News Archive
          </a>
        </div>
      </body>
    </html>
  </xsl:template>
  <xsl:template mode="NewsCategory" match="*">
    <xsl:variable name="id" select="@Id" />
    <li>
      <xsl:value-of select="@Name" />
      <ul>
        <xsl:apply-templates mode="NewsItem" select="$items/*[@Category.Id = $id][position() &gt; count($items/*[@Category.Id = $id]) - $pagesize]">
          <xsl:sort select="@Date" order="ascending"/>
        </xsl:apply-templates>
      </ul>
    </li>
  </xsl:template>
  <xsl:template mode="NewsItem" match="*">
    <li>
      <xsl:if test="position() = 1" >
        <xsl:attribute name="class" >First</xsl:attribute>
      </xsl:if>
      <xsl:if test="position() = last()" >
        <xsl:attribute name="class" >Last</xsl:attribute>
      </xsl:if>
      <span class="NewsDate">
        <xsl:value-of select="user:FormatDate(@Date)"/>
      </span><br/>
      <span class="NewsTitle">
        <a href="/page({/in:inputs/in:param[@name='NewsDetailsPage']})?NewsID={@Id}&amp;News={@Title}">
          <xsl:value-of select="@Title"/>
        </a>
      </span>
    </li>
  </xsl:template>
</xsl:stylesheet>

Listing 7: The NewsLatest template code