Where am I? Skinning the SDL Tridion Content Manager Explorer.

To provide context between similarly-looking SDL Tridion environments, consider Skinning the Content Manager Explorer as described on SDL Live Content.

Simplified, slightly modified, and with some pics. This is a fairly quick and easy way to make your CMS environments stand out from each other. For the full power of CME Themes, head on over to YATB.

  1. Make a Copy. Go to %TRIDION_HOME%\web\WebUI\Editors\CME\Themes\Carbon\Environments\. Copy one of the .css files, I used Development.css and renamed it CreateAndBreak.css.
  2. Change colors. Update the background-color hex values. Inspired by this Mihai’s “Getting Dark” series, I tried #444 to make the background bar and splitters dark gray.
    * Views */
    .itemview .tabwrapper,
    .itemview #MasterTabControl_switches
    {
    	background-color: #444; /* updated */
    }
    /* Dashboard */
    .dashboard #DashboardTitlebar,
    .dashboard #DashboardSplitter
    {
    	background-color: #444; /* updated */
    }
    /* Overrides */
    .titlebar .extensions .time
    {
    	background: none;
    	padding: 3px 0 3px 10px;
    	margin-left: 0;
    }
    .titlebar .extensions .time label
    {
    	padding: 0 5px 0 0;
    }
    #TitlebarContainer
    {
    	background-color: #444; /* updated */
    }
    /* Environment name on titlebar */
    .titlebar .extensions .environment-name
    {
    	max-width: 250px;
    	overflow: hidden;
    	white-space: nowrap;
    	font-size: 10px;
    	color: #fff;
    	float: left;
    	padding: 5px 10px 3px 10px;
    }
    
  3. Add a Logo. Optionally updoad an image (95w x 24h pixels) and change the background-image url.
    .tridion .abouttridion .button, .tridion .abouttridion .button.mouseover, .tridion .abouttridion .button.mousedown {
      background-image: url("/WebUI/Editors/CME/Themes/CreateAndBreak/cb-95-24.png"); /* updated */
      background-position: 0 50%;
      background-repeat: no-repeat;
      border-width: 0;
      cursor: pointer;
    }
    
  4. Make a “style” Group. Update %TRIDION_HOME%\web\WebUI\Editors\CME\Configuration\CME.config. Add a new group:
          <!-- original -->
          <cfg:group name="Tridion.Web.UI.Editors.CME.Theme.Environments.Development">
            <cfg:fileset>
              <cfg:file type="style">{ThemePath}/Environments/Development.css</cfg:file>
            </cfg:fileset>
          </cfg:group>
    	<!-- added 1/23/2013 -->
          <cfg:group name="Tridion.Web.UI.Editors.CME.Theme.Environments.CreateAndBreak">
            <cfg:fileset>
              <cfg:file type="style">{ThemePath}/Environments/CreateAndBreak.css</cfg:file>
            </cfg:fileset>
          </cfg:group>
  5. Update the environment node. Then find the commented out node and uncomment or make a copy.
        <!--environment>
    	<name>Development</name>
    	<resourcegroup>Tridion.Web.UI.Editors.CME.Theme.Environments.Development</resourcegroup>
        </environment-->
        <environment>
            <name>CreateAndBreak</name>
            <resourcegroup>Tridion.Web.UI.Editors.CME.Theme.Environments.CreateAndBreak</resourcegroup><!-- matches group above -->
        </environment>

  6. Increment the modification attribute’s value in %TRIDION_HOME%\web\WebUI\WebRoot\Configuration\System.config
    <server version="6.1.0.55920" modification="4">

Not quite a full re-skin, but enough to make me wonder who messed with my VM. Note the server name shows and the tiny logo. In older versions of Tridion you could do the same by updating \Tridion\web\images\other\topBG.gif for the gradient and Tridion\web\library\css\colourstyle.css for the bar in item pop-ups.

Thanks to the SDL Live Content contributors and technical writers for including the example I’m working from. If you have feedback on the examples, have a question, or find the rare typo, do leave a comment on SDL Live Content. The team appreciates it.

Extending the SDL Tridion dashboard in both the CME and Experience Manager

When you open up the SDL Tridion UI for the first time you are directed to the SDL Tridion tab which shows you a welcome screen, this is sometimes also referred to as the SDL Tridion dashboard. If you open the Experience Manager view, you actually see that the first tab is called “Dashboard”. This dashboard contains useful information for an editor and is even extensible, allowing for a rich user experience on your extensions. Typical things are settings for your own extensions, or perhaps user specific pages (like a custom page, but then tailored to the logged in user) since you have all the UI capabilities available here.

Extending the SDL Tridion dashboard is all about DeckPages, OptionsPanels and ExtendedAreas, basically nothing more than a couple of TridionUserControls or .ascx pages. To explain the possibilities I’ve created an example UI extension, which adds a new option to the SDL Tridion dashboard OptionsPanel (menu item on the left hand side of the dashboard) in both the Content Manager Explorer (CME) and the Experience Manager (SiteEdit). This option loads a DeckPage when selected, and inside that DeckPage you can add user controls through ExtendableAreas. Below is a screenshot of how this extension looks in the CME.

Now let’s start with taking apart the main deckpage, it is a very simple user control which exposes two extendable areas, one for the buttons and body part for the content. The html of this deckpage (an .ascx) looks as follows:

<%@ Control Language="C#" AutoEventWireup="true" Inherits="Example.Controls.DeckPages.MainDeckPage" %>
<div id="MainDeckPageOverlay" class="stack horizontal">
  <div class="label stack-elem">
    <asp:Label runat="server" Text="Stack Overflow" />
  </div>
  <div class="separator line stack-elem"></div>
  <div id="MainDeckPageButtonsHolder" class="line stack-elem">
    <c:extendablearea id="MainDeckPageButtons" runat="server"></c:extendablearea>
  </div>
  <c:extendablearea id="MainDeckPageBody" runat="server"></c:extendablearea>
</div>

To add the buttons to the page, we need to look at the configuration of the UI extension (an editor extension). Since our deckpage defined two extentable areas, we can now simply add a button under the ext:extendableareas element in the configuration, applying it to the TridionDashboard view (or DashboardView for SiteEdit) and for the control, we reference the id of the extendablearea control (MainDeckPageButtons in this example). See below a snipped of the configuration, adding the first button.

<ext:extendedareas>
  <ext:add>
    <ext:extension assignid="FirstButton" name="Newest">
      <ext:control>~/Controls/ExtendedAreas/FirstButton.ascx</ext:control>
      <ext:pagetype></ext:pagetype>
      <ext:renderinblock>false</ext:renderinblock>
      <ext:apply>
        <ext:view name="TridionDashboard">
          <ext:control id="MainDeckPageButtons" />
        </ext:view>
      </ext:apply>
    </ext:extension>
  </ext:add>
</ext:extendedareas>

The button itself is actually another deckpage or user control, containing a button control in its html. Below the buttons there is another extendable area in which we can place the content for the selected button. This is the same as with the button, but now we apply it to the control with the id: MainDeckPageBody. This user control loads another deckpage which contains our actual content, it does that by adding a deckpage control:

<%@ Control Language="C#" AutoEventWireup="true" Inherits="Example.Controls.ExtendedAreas.FirstButtonWidgetsView" %>
<div class="widgetsviewfilter" id="FirstButtonWidgetsViewFilter" style="height: 25px;">
    <div id="FirstButtonWidgetsViewFilter_addressBar" class="addressbar" c:separator="" c:itemsize="20">
        <span class="addressbaritem home" c:uri="edu:training" title="First (edu:training)" tabindex="1">First View</span>
    </div>
</div>
<c:deck runat="server" id="FirstButtonPages" class="stack horizontal">
    <c:DeckPage runat="server" id="FirstButtonPage" SourceEditor="Example" ExternalControl="~/Controls/DeckPages/FirstButtonPage.ascx" PageType="FirstButtonPage" />
</c:deck>

So far we have used 4 different deckpages (user controls) and with that, we have extended the options panel (left hand navigation) and added a button with a content area below it. Inside the actual page for the first button (FirstButtonPage.ascx) there is an iframe in which I load my content to keep this example simple. But basically you can build up this page like any other UI page, with (custom) user controls.

To wire it all up, there is a JavaScript and Stylesheet file available for all user controls (.ascx pages). These files are grouped together using the editor configuration file again. Here is an example of the group for the main deckpage.

<cfg:group name="Example.Controls.DeckPages.MainDeckPage">
  <cfg:fileset>
    <cfg:file type="script">/Controls/DeckPages/MainDeckPage.ascx.js</cfg:file>
    <cfg:file type="style">/Controls/DeckPages/MainDeckPage.ascx.css</cfg:file>
  </cfg:fileset>
</cfg:group>

The name of the group is used in the code behind (.cs) file for the MainDeckPage.ascx, to reference its control resources, through this mechanism the JavaScript and Stylesheet files are loaded inside the SDL Tridion UI and made available. The ControlResources attribute is used for this as follows.

using Tridion.Web.UI.Controls;
using Tridion.Web.UI.Core.Controls;
namespace Example.Controls.DeckPages
{
  [ControlResources("Example.Controls.DeckPages.MainDeckPage")]
  public class MainDeckPage : TridionUserControl
  {
  }
}

As you can see there is no further C# code for the deckpage, all of the logic is handled in the JavaScript file. I won’t try to explain all the JavaScript code in this article, but will let the code speak for itself. I’ve placed the entire example UI extension on SDL Tridion World, complete with all source code, so you can install it and look for yourself what does what. Feel free to take it apart completely or reuse as a basis for your own UI extensions.

What was most revealing to me, while building this extension, was the fact that it shows how the UI extension framework really works. By adding extendable areas to your own deckpage, you are making your extension extensible itself. It’s directly used for adding the buttons and deckpages for the button content, but you also made it possible for somebody else to extend your extension again.

Avoid Static Variables in Tridion Templates

I’ve recently helped a client resolve some interesting issues with their Tridion template building blocks. The issue was that when publishing the first time desired results were produced by the template. However, when publishing many pages, or republishing the same page/component presentation, subsequently produced strange output results.

The logic in our TBB parsed the Output item in the package and made various manipulations to it. I won’t get into the details of that. The issue was due to having static private variables declared within the TBB. These variables were various data structures and primitives manipulated by methods within our ITemplate-inherited class. The methods themselves had to also be declared as static. Refactoring the code away from “static” resolved the issue.

Here is why you cannot use static variables in Tridion templates: Primarily because they become global shared variables across all publish threads. Here is what the Tridion TOM.NET Session and Engine class API states regarding this matter:

IMPORTANT NOTE: Session objects are not thread-safe; you must not share Session objects across threads. Furthermore, the thread that creates a Session object should be in a Single Threaded Apartment (STA), since the Session will internally create an Apartment Threaded COM object. See SetApartmentState(ApartmentState).

The Tridion Session object, which the Engine uses (or, as the docs put it, “aggregates”) subscribes to the Single Threaded Apartment model (STA).

An STA is basically a model where the server controls the threads, and each individual thread must not spin off additional sub-threads that depend on the STA-controlled objects. So in our case: the Tridion CM is the server that spins off and controls threads (e.g. we can publish many items simultaneously), and each template is executed within it’s own thread. So if we develop a template that spins of other threads, the common memory across threads, i.e. static variables, isn’t managed. So STA threads can overwrite each other.

Here is a snippet from an article explaining STAs in much more detail:

…all access to global variables and functions of the server will need to be serialized properly because more than one object may try to access these from different threads. This rule also applies to class static variables and functions. [http://www.codeproject.com/Articles/9190/Understanding-The-COM-Single-Threaded-Apartment-Pa]

So in a common scenario, when is it appropriate to use “static” within a TBB? Well, how about in procedural methods that take some inputs, create new objects, massage them and return them. In other words, if the method is self-contained. A Utility class is a common such scenario.

In conclusion, don’t use static variables inside your TBBs or Event System code.

DWT strings, integers and repeatable fields

I was reviewing a solution I’ve been working on and noticed that at one point I was pushing out a ‘QuantityOfListItems’ from C# into the package - and couldn’t recall why.

On further investigation I saw that I was using this in the DWT to detect when I was on the last list item so I could compare this with the TemplateRepeatIndex value. Although it all worked fine it felt like overkill to rely on C# for something so trivial and didn’t add any value so I had a scoot around.

I found a CollectionLength built-in function that looked like it might be just what the Dr. ordered…

So… the reason for the post… It took quite some phaffing to tweak the syntax for this - just due to the fact I’ve had my head in C# code more than I have DWT lately so I thought I’d share it!

All I need to do is check

  • if the current index of the item in the repeatable field is the first then print the opening tags
  • write out the repeated syntax/value for each instance of the repeatable field
  • if the current index of the item in the repeatable field is the last then print the closing tags
First attempt:
<!-- TemplateBeginRepeat name="Component.Fields.repeatField" -->
<!-- TemplateBeginIf cond = "TemplateRepeatIndex == 0 " -->
<ul>
<!-- TemplateEndIf -->
<li>@@Field@@</li>
<!-- TemplateBeginIf cond = "TemplateRepeatIndex == @@CollectionLength("repeatField")@@" -->
</ul>
<!-- TemplateEndIf -->
<!-- TemplateEndRepeat -->

OK, so the obvious error - TemplateRepeatIndex is 0 based whilst the CollectionLength returns a 1 based result! The other syntax is pretty standard so we’ll concentrate on the test for the last index item.

<!-- TemplateBeginIf cond = "TemplateRepeatIndex == @@CollectionLength("repeatField")-1@@" -->

No Joy. So I have another scoot around and find an interesting StackOverflow post reminding me the value returned is a string. As we need to perform an integer calculation and comparison we need to parse the value to an integer.

<!-- TemplateBeginIf cond = "TemplateRepeatIndex == @@parseInt(${CollectionLength("repeatField")})@@-1" -->

Now I’m using the debug method Frank van Puffelen describes in his StackOverflow posting and all appears well - I can see the CollectionLength and the TemplateRepeatIndex are both 1 and the end tag still isn’t written out.

See the deliberate error (well it wasn’t deliberate and it was one of those annoyingly simple things that I’m sure others will trip up on!). Although our first comparison with TemplateRepeatIndex and == 0 above is with an integer and it works as we want (even if the comparison is against a non-zero integer) I figured I’d try to parseInt the TemplateRepeatIndex…

<!-- TemplateBeginRepeat name="Component.Fields.repeatField" -->
<!-- TemplateBeginIf cond = "TemplateRepeatIndex == 0 " -->
<ul>
<!-- TemplateEndIf -->
<li>@@Field@@</li>
<!-- TemplateBeginIf cond = "@@parseInt(TemplateRepeatIndex)@@ == @@parseInt(${CollectionLength("repeatField")})@@-1" -->
</ul>
<!-- TemplateEndIf -->
<!-- TemplateEndRepeat -->

Success!

Hopefully this simple pattern can assist you in not having to resort to C# as I’d figured I had to originally.

Quick Tip: Turn off verbose template logging in Tridion Event Log

As a good developer, you write out useful information and debug messages in your .NET TBBs using the built in templating logger. This is great when running the TBBs in the template builder to give a bit of extra info about the processing. However, by default info messages will be logged in the Tridion Event Log of your Publisher server, causing log bloat, and making it harder to see important Error and Warning messages. Its really easy to turn this off, but I am not sure I have seen it documented anywhere. Heres how…

Continue reading

Localization != Personalization. Why CXM Shouldn’t Affect Your SDL Tridion BluePrint… Much.

I’m seeing challenges and confusion with BluePrinting and Targeting in BluePrint workshops and in discussions about profiling and personalization. Don’t let the rise of “Customer Experience Management” (CXM or CEM for TLA fans) adversely affect your SDL Tridion BluePrint. Localization != Personalization (!= is code, literally, for “not equals”).

Continue reading

Fixing a busted Tridion publisher

So I have to work this evening… want to know why? Because today my Tridion virtual machine magically stopped publishing after a reboot, I could smell Java libraries from the start!

Let me explain…

I went to bed feeling pretty satisfied after a long, hard day of coding. Same as any other day I simply closed the lid of my laptop to send my computer into hibernation, I foolishly (I admit) left my VM running.

Continue reading

Adding page-free content to your site with ASP.NET MVC

Most implementations I have worked on have sections of the website which consist of some kind of listing, which links through to pages containing detailed content. The details pages themselves contain a single ‘Full Article’ component presentation, and are often auto-created and published using the event system. As such, the pages do not really serve any purpose other than providing a definition of the url for an article.

This article shows an alternative approach to get rid of pages for a certain section of your site and use the features of ASP.NET MVC to show content without losing ‘real’ urls

Continue reading