Tuesday, March 25, 2008

Team Development

A full discussion of team development exceeds the scope of this blog; however, a brief mention of the relationship between team development and content tree design is worth mention. Typically, developers work in one of two ways: using a shared development database or a local database. In the former scenario, all changes are saved to a common system; in the latter scenario, data is changed locally and moved (using the Packager) to an integration server.

A simple rule of thumb in either scenario is to reduce the incidence of developers writing to the same item or subitems in the content tree. In the former scenario (a single development database), this can create unintended consequences due to the fact that most developers work as Administrator users. This means that common features such as item locking do not apply to them. Developers can easily overwrite each others' changes if they are working on the same part of the content tree simultaneously.

In the latter scenario (isolated development), challenges emerge when moving data to an integration server. Choosing the correct installation options in the Package Installer is often confusing and developers can end up overwriting each others’ changes.

In any case, communication and coordination between developers is essential to ensure that the data in the content tree is accurate.

Monday, March 24, 2008

Packaging Nodes

While it is typically not a first consideration to design your content structure to facilitate the creation of packages, it is worth noting that the content tree can be organized to make the difficult process of generating packages easier. Here, the simplest of recommendations applies: Organize project assets into logical folders whenever possible. For example, when managing multiple sites in a single instance of Sitecore, organize templates into site-specific folders. This makes it easy to use the "Add with Subitems" command in the Package Designer when adding items statically.

Monday, March 17, 2008

Sorting Content Items

By default, Sitecore sorts content items based on their sortorder attribute. Sortorder describes how a single content item should be sorted relative to its siblings. (If the sortorder attribute is not set, Sitecore uses the SubItems Sorting value of the parent item. See here for more information: http://sdn5.sitecore.net/Scrapbook/Understanding%20the%20Sort%20Order.aspx.) To set the sort order of an item, business users can click on the sort commands in the ribbon. The sort commands provide commonly required options:



Clicking on one of these commands updates the sortorder attribute of the selected content item:


Clicking on the sorting options allows business users to sort the subitems of the current item based on a number of criteria:



Rendering logic will implicitly recognize this sort order, such that the following rendering will respect the sortorder field:

    <xsl:for-each select="$home/item[@key='vehicles']/item">
      <sc:link>
        <sc:image field="menu image" />
        <sc:text field="menu title" />
        </sc:link>
    </xsl:for-each>

Sitecore allows developers to implement their own sorting algorithms (http://sdn5.sitecore.net/Scrapbook/Understanding%20the%20Sort%20Order.aspx). Algorithms can be specified under /sitecore/system/settings/subitems sorting. For example, to sort items by a date field, developers can use the following approach:

public class ItemComparer : IComparer
{
private string _fieldName;

public ItemComparer()
{
}

public ItemComparer(string fieldName)
{
_fieldName = fieldName;
}

public int Compare(object x, object y)
{
Item item1 = x as Item;
Item item2 = y as Item;

if (_fieldName == null)
{
_fieldName = "date";
}

Sitecore.Data.Fields.DateField date1 = (Sitecore.Data.Fields.DateField)item1.Fields[_fieldName];
Sitecore.Data.Fields.DateField date2 = (Sitecore.Data.Fields.DateField)item2.Fields[_fieldName];
return System.DateTime.Compare(date1.DateTime,
date2.DateTime);
}
}

This class can be used in your code-behind as follows:

// get an array of the Items:

Item[] i = Sitecore.Context.Database.SelectItems("");

// sort it using your comparer:

Array.Sort(i, new ItemComparer());

// use the sorted array:

foreach (Sitecore.Data.Items.Item thisItem in i) {
// do something with each item
}

Tuesday, March 11, 2008

Content Search

In some projects, developers must address requirements that involve simple searches. For example, imagine a firm that provides trainings at store locations around the local area. Each training has a predictable set of attributes such as Title, Start Date and Instructor. End users can select from a list of classes and view all of the locations where the training is being offered. Effectively, the system performs two searches: the first query returns a list of all classes; the second query returns a list of locations where a specific class is being taught. The system may provide further flexibility, wherein choosing a location lists all of the instructors teaching the class. In this case a third query will be required.



Note that here we are referring to a search of content nodes, not a website search that would typically be addressed by a site search engine. The following analysis will assume that developers are either using XPath or Sitecore Query to perform query operations.

The performance of this feature and the strategy for its implementation may be dramatically different depending on the design of the content tree. The following may be the initial design for the project, based on the logical structure of business units:

I. Home
  a. Locations
    i. Store A
      1. Classes
        a. Class A
        b. Class B
      2. About Us
    ii. Store B
      1. Classes
        a. Class A
        b. Class B
      2. About Us

This intuitive structure may easily map on to the business hierarchy, facilitate the development of navigation elements and simplify the implementation of content markers.

To query the above content structure, however, requires the use of descendants queries that may slow site performance. A Sitecore Query of /sitecore/content/home/locations//*[@@templatename=’class’] would return a result set including all classes; however, the system would have to traverse almost the entire content tree to access the desired results. Similarly, a Sitecore Query of locations that teach the selected class would require an additional descendants search along with further analysis of content items to determine available locations.

The larger the number of locations, the slower the queries will perform. The SDN provides guidance on optimizing the performance of various query scenarios (http://sdn5.sitecore.net/Reference/Using%20Sitecore%20Query.aspx); however, in this case, a reorganization of the content tree may be in order. Consider the following alternative:

I. Global
  a. Classes
    i. Class A
      1. Instance A
      2. Instance B
    ii. Class B
II. Home
  a. Locations
    i. Store A
      1. About Us

Here, the classes are organized under /sitecore/content/Global/Classes. Each class has any number of instances, which include attributes for Start Date, Instructor and Location. With this organizational approach, the queries are simplified and provide significance performance advantages. A query of /sitecore/ content/Global/Classes/* will return a list of classes without requiring a descendants query. Retrieving a list of locations for a particular class, again, avoids descendants queries. The query /sitecore/content/Global/Classes/Class A/* will return all class Instances for a particular Class. This list of Classes will need to be analyzed to generate a list of locations.

Optimizing the content tree for search performance is a broad topic and exceeds the scope of this document. However, developers should keep this consideration in mind when designing the content tree. Other approaches – such as leveraging the Links Database, creating search metadata or using search engine APIs – should also be considered fully when optimizing search-driven features.

Wednesday, March 5, 2008

Navigation Elements

The design of your content tree can dramatically impact the ease of implementing navigation elements such as hierarchical navigation or breadcrumbs.

When the content tree mirrors the information architecture of your web site, generating hierarchical navigation is extremely simple. An XSL code snippet could be as straightforward as the following:

  <xsl:for-each select="$home/item[@template='section']">
    <div>
      <sc:link>
        <sc:text field="menu title" />
      </sc:link>
    </div>
  </xsl:for-each>

In this simple example, the XSLT loops through the children of the home item and outputs a link to each child that is based on the Section template. The simplicity of this rendering is dependent upon the mirroring of the content tree and the information architecture of the site. When possible, this is a very desirable way to design the content hierarchy due to the development productivities that can be realized.

Depending upon what should appear in the site navigation, however, this exact mirroring may not be possible. Business users may want to control the items listed in the navigation based on editorial decisions rather than a static information architecture. Consider a requirement for the following items to appear in the navigation: Specials, CEO Blog, Products and About Us. These items, however, may be scattered throughout the content tree:

I. Home
  a. Products
    i. Specials
    ii. Product A
  b. About Us
    i. CEO Blog

There is no simple logic to determine which items will appear in the navigation and – even if there were – this logic may change over time. To address this, developers may wish to allow business users to configure the navigation themselves. One way to accomplish this requires that the navigation is described in metadata items, possibly in the /sitecore/content/global folder.

I. Content
  a. Global
    i. Navigation
      1. Nav 1
      2. Nav 2
      3. Nav 3
      4. Nav 4

Each of the Nav elements in the above example is based on a template with a Navigation Item field. This field may be a tree list control that allows business users to select an arbitrary item from the content tree. Business users may sort the children of /content/global/navigation in the order they should appear in the navigation. A simple rendering would be:

  <xsl:for-each select="/*/item[@key='content']/item[@key='global']/item[@key='navigation']/item">
    <div>
      <xsl:variable name="sourceItem" select="sc:item(sc:fld('navigation item', .), .)" />
      <sc:link select="$sourceItem">
        <sc:text field="menu title" select="$sourceItem" />
      </sc:link>
    </div>
  </xsl:for-each>

A perhaps simpler approach (though more complex in XSL) would be to implement a single Navigation item:
I. Content
  a. Global
    i. Navigation

The Navigation item would have a field called “Menu Items” of type treelist whose source is the /sitecore/content/home item. Business users can choose which items they want to appear in the navigation and sort them based on editorial decisions.

Yet another way to tackle this problem would be to create a base template for your site with a “Show in Navigation” field. Your rendering logic could perform a descendants query starting with the home item to check for the value of the field. While the solution is simple, this query could create extreme overhead problems as descendants queries can be very expensive.

Rendering breadcrumbs poses similar challenges to those described with hierarchical navigation. When the page navigation mirrors the content tree, rendering breadcrumbs is trivial from a coding perspective:

  <xsl:for-each select="ancestor::item">
    <xsl:if test="position() > 2">
      <sc:link>
        <sc:text field="menu title" />
      </sc:link> >
    </xsl:if>
  </xsl:for-each>
  <sc:text field="menu title" />

If the user has navigated to the /sitecore/content/Home/Products item, the above rendering will output: Home > Products.

When the content tree does not mirror the information architecture of the website, breadcrumbs become more complicated. Imagine the following content hierarchy:

I. Home
  a. News
    i. 2007
      1. January
      2. February
        a. 01
          i. News Story 1
          ii. News Story 2

In the example above, News Stories are structured by the year, month and date they are entered. However, business requirements may dictate that the breadcrumb exclude the year, month and date items and display all News Stories as children of the News section. For example, the breadcrumb for News Story 1 should be: Home > News > News Story 1. To implement this, a simple filtering of content items by template may be sufficient:

  <xsl:for-each select="ancestor::item">
    <xsl:if test="position() > 2 and @template != 'folder'">
      <sc:link>
        <sc:text field="menu title" />
      </sc:link> >
    </xsl:if>
  </xsl:for-each>
  <sc:text field="menu title" />

The more your content tree deviates from your site’s information architecture, the more complex your rendering logic will need to be and the more reliant your renderings will be on metadata. This should not imply that your content tree needs to be determined by ease-of-coding considerations. However, where developer productivities can be achieved by simplifying content organization, this should be considered.