DataExtenders are one of the coolest features in Tridion 2011 – and allow us to add our own column into the default Tridion ListView for all users.  We can save time when finding content – no need to open 10s of Components to find a specific value. However, with this new power comes a greater responsibility, and we must take extra care to make our code lightning-fast and test it well. In this example I will provide a working solution for adding a Metadata field column as well as explain how to do something similar yourself. All we need now is time and a little bit of luck to pull it off.

Tridion DataExtenders edit the response sent back from the GUI to the Browser.  In other words, our code runs every time for every user using the Tridion GUI.  DataExtenders are not limited to adding additional columns to the ListView.  Tridion uses this approach for the recent SDL World Server connector where they provide extra fields in the Schema edit screen, for example.  However, we’ll first start here on the ListView as it is the most useful GUI Extension for our scenario.

Tridion editors in my current project add the product sku to the Metadata field of each product.  When changing content or updating products they often have a product name or sku – and having this sku visible in the ListView provides a big time-saving.  The GUI also nicely provides the sort and filter functionality available from other columns to our new column.  However, to add this extra column our code will take time to find the sku value – and this will add a little bit to the response time of the GUI – especially for folders with lots of Components (100s).  So, it is a trade-off – will your users wait 1 second more for every 100 items in a folder if it means they can see the product Sku?  Do most of your folders contain less than 100 items?  In my case, yes, it is worth it, and we almost never have more than 100 items in a folder.

Example, note the Metadatafield column:

Metadata Column added to Tridion GUI

Getting started – Download the Example and Run the Code

1.  Get the example here: https://github.com/rcurlette/DataExtenderMetadataCol.  Open in Visual Studio 2010.

2.  Set the metadata fieldname.  Open the AddMetadataColumn.cs file, change ‘article_number’ to your fieldname.

3.  Compile.  Copy ALL files in the VS output folder (/bin/Debug) to the CMS Server at /Tridion/web/WebUI/WebRoot/bin.

4.  Create a new Folder on the CMS server for your DataExtender GUI Extension.  For example, create the DataExtender folder here: /Tridion/web/WebUI/Editors/DataExtender 

5.  Copy the DataExtender.config file to the folder above in step 4.

6.  Add the DataExtender config location to the System.config file in Tridion\web\WebUI\WebRoot\Configuration\System.config.

<editors default="CME">
  ...
  <editor name="DataExtender">
    <!-- DLL Files for DataExtender to be deployed to /Tridion/web/WebUI/WebRoot/bin -->
    <installpath>
     C:\Program Files (x86)\Tridion\web\WebUI\Editors\DataExtender\
    </installpath>
    <configuration>DataExtender.config</configuration>
    <vdir/>
  </editor>
</editors>

7.  Refresh the GUI and behold your new GUI ListView Column

Overview

How did we make that happen?  Well, DataExtenders are a special type of GUI Extension and allow us to modify the Response the GUI sends to the clients.  This is quite different than ContextMenu GUI Extensions.  Here our code is in ASPX / C#  while the ContextMenu code is mostly in JavaScript.  It feels like we’re hooking into a different aspect of the Tridion GUI and it requires a different approach – not only to how we develop, but also to how we test the code as well as how we debug and deploy.  Read on to find out how this was built and learn some tips and tricks for building your own DataExtenders.

Creating a new DataExtender

Speed is our primary concern when writing DataExtender code.   Not how fast you can type, but how fast your code executes.   The code is executed for every response – and we filter the response for the GetList command; all items in the listview.  Test the code with a folder containing hundreds of items – not only a few!  This cannot be overstated and is why I start with this disclaimer.  With the Tracing log feature you can view how much time your code is taking and adjust accordingly.  This is the most important and limiting aspect of developing a DataExtender since it affects all users and all content.

Create the .NET Class Project –  Create a new .NET Class Project and add a new Class file.

Extend the DataExtender Class – First we need to extend the DataExtender class.

public class AddMetadataColumn : DataExtender

Add the Namespace:

using Tridion.Web.UI.Core.Extensibility;

Reference the Assembly:  (located in Tridion/web/WebUI/WebRoot/bin)

Tridion.Web.UI.Core

Override the ProcessResponse Class

The ProcessResponse class is the main entry point for the DataExtender and is called for different types of GUI responses.  Notice here we check the command to see if it is for ‘GetList’.  This is critical for filtering out other requests and only listening to the listview.

This code is from a great example written by GUI Hacker Serguei Martchenko at http://www.sdltridionworld.com/community/2011_extensions/parentchangenotifier.aspx.

public override XmlTextReader ProcessResponse(XmlTextReader reader, PipelineContext context)
{
  XmlTextReader xReader = reader;
  string command = context.Parameters["command"] as String;
  if (command == "GetList")  // Code runs on every GetList
  {
    try
    {
      xReader = PreprocessListItems(reader, context);
...

Disclaimer: Any errors in the PreprocessListItems breaks the GUI.  If your XmlTextReader does not return at least the original data you will have missing data in the GUI.

 

ReCreate the <tcm:Item /> node, return as XmlTextReader

As mentioned above, we need to pass back at least the data that Tridion is sending back.  Thanks to the example code from Serguei we only need to follow a couple of simple rules.

1.  Re-write all existing attributes to the <tcm:Item node.

 xWriter.WriteAttributes(xReader, false);

2.  Add code and logic to get additional data.

attrValue = GetMetadataValue(comp, "article_number");

 

Getting the metadata value

In the example code I call a GetMetadataValue method for getting my data.

private string GetMetadataValue(Component comp, string fieldname)
{
  string value = "";
  xmlDoc.LoadXml(comp.GetXML(Tridion.ContentManager.Interop.TDSDefines.XMLReadFilter.XMLReadDataContent));
  string xPath = String.Format("//*[local-name()='{0}']", fieldname  );
  if (xmlDoc.SelectSingleNode(xPath) != null)
  {
    value = xmlDoc.SelectSingleNode(xPath).InnerText;
  }
  return value;
}
The performance criteria of the Metadata field example pushed me to use a TDSEWrapper instead of the Core Service.  The Core Service was quite slow – although I did not use Jaime’s approach with the tcp_Binding and instead used the default http_Binding.  I tried to create the WCF client once and re-use it, but maybe I did it wrong?  Anyways, it was the slowest of my examples.  Sergeui Martchenko also uses a TDSEWrapper for his code samples – maybe for the same reason?
Performance tests with folder of 250 Components:
Core Service 12 seconds
TDSE Object Model 12 seconds
TDSE GetXml 2 seconds

The load times are surprising and I would look to persisting Tridion data to an external system such as Redis or a database to improve performance times.  This cannot be emphasized enough – the Tridion API might not be fast enough – and you should seriously think about getting the data from another source.

3. Display the value with XPath in the DataExtender.config file

 <column xmlns=”http://www.sdltridion.com/2009/GUI/extensions/List”
                             id=”Metadatafield” type=”data” title=”Metadatafield”
                             selector=”@metadataFieldValue” translate=”String” />

Debugging – Enabling GUI Tracing

While writing this example I used the Trace debugging a lot – not only for timing certain actions – but also in my try/catch blocks for writing out errors.  This is the only way to Debug DataExtender code and it works well.  However, our log file grows REALLY fast – so you might not want to keep it running all the time.  In the C# code we can use Trace.Write to write output to the GUI Trace log.  Don’t use Trace.WriteLine – it doesn’t work.  This is also the best way to know our DataExtender is being executed. If we do not see the DataExtender name in the log file then it is not being loaded.

1.  Backup original file. Rename Tridion\web\WebUI\WebRoot\bin\Tridion.Web.UI.Core.dll to Tridion\web\WebUI\WebRoot\bin\Tridion.Web.UI.Core.dll.bak

2.  Copy Tridion.Web.UI.Core.dll Trace DLL from /trace to to /bin.  Location:  \Tridion\web\WebUI\WebRoot\bin\trace\Tridion.Web.UI.Core.dll

3.  Turn on Tracing in \Tridion\web\WebUI\WebRoot\Web.Config

<compilation debug="true" defaultLanguage="c#">

4.  Confirm Trace File is Created.  Refresh GUI, Trace log, Tridion.Web.trace, created in the folder  \Tridion\web\WebUI\WebRoot.

To delete Trace file – stop IIS (net stop w3svc) and then delete.

* Warning – This produces a LOT of log output – you do not want to do this in Production.

Example Trace code to output start time

Trace.Write("==========================Start PreprocessListItems " + System.DateTime.Now.ToShortDateString() + ", " + System.DateTime.Now.ToLongTimeString() + Environment.NewLine);
Trace File Output – Notice my GUI Extension is there ‘AddMetadataCol’.

Example Trace Code to display <tcm:Item XML

return xReader;  //around line 157
Trace.Write(sWriter.ToString() + Environment.NewLine);
// Trace.WriteLine breaks code

Quickstart:  Sample Empty DataExtender Class

If you are starting a fresh DataExtender you can use this class to get started instead of my  AddMetadataColumn.cs class.

 

This code is the basis of every DataExtender – and does nothing more than re-writing the XML output.

 

1.  Rename Example Classname to yours.

2.  Add your custom code to append content to the tcm:item node.

3.  Use Trace.Write to write to logTrace.Write(sWriter.ToString() + Environment.NewLine);

* Trace.WriteLine does NOT work – will break code.

4.  Update the Tridion Project References

using Tridion.ContentManager; // C:\Program Files (x86)\Tridion\bin\client
using Tridion.Web.UI.Core.Extensibility; // C:\Program Files (x86)\Tridion\web\WebUI\WebRoot\bin

 

Compile, Deploy, and Update DataExtender Config

As with all GUI Extensions – getting the right configuration is half the battle.

1. Copy the GUI Extension DLL and ALL other DLLs in the /Debug/bin (or /Release/bin) from the output of your VS build command to the Tridion Server folder \Program Files (x86)\Tridion\web\WebUI\WebRoot\bin

2. Add extension.config file to the /Editors/YourDataExtender folder. Check out the relationships between your Namespace, Classname, and AssemblyName for the config.

Take special note of the DLL Name (from the Project Properties window) and how it corresponds to the Config.  This was the most difficult part of writing the DataExtender for me.

type=”Namespace.Classname, Assemblyname” 

DataExtender Assembly Name
DataExtender Assembly Name
DataExtender Class File
DataExtender Class File

Tridion DataExtender Config fileTridion DataExtender Config File

3. Update the System.config file with the location of your config file

 

Add the DataExtender GUI Extension to the System Config

As soon as you do this your GUI Extension is ‘enabled’ and any errors / issues will be seen immediately in the GUI for all users.

Save in:  C:\Program Files (x86)\Tridion\web\WebUI\Editors\DataExtender\ DataExtender.config

Update the System.config file 

<editors default="CME">
  ...
  <editor name="DataExtender">
    <!-- DLL Files for DataExtender to be deployed to /Tridion/web/WebUI/WebRoot/bin -->
    <installpath>
     C:\Program Files (x86)\Tridion\web\WebUI\Editors\DataExtender\
    </installpath>
    <configuration>DataExtender.config</configuration>
    <vdir/>
  </editor>
</editors>

 

DataExtender Tips:

Be Fast, be very fast – Your code is going to slow down the GUI, no question about it. But, how fast can you make your code? This is a key factor to the success of rolling out your GUI Extension.

Document it –  your code will be executed for every list view in every Publication of the GUI. Knowing what is happening there will help all developers maintain it in the future. Flow diagrams are good here.

Use your own Dev Server –  I broke the GUI a lot of times before getting it to work. So – best to do this on your own local instance or in the middle of the night.

Troubleshooting:

Nothing shows in the lists – Comment out your extension in the System.config.  Does it work now?  Ok, you definitely broke it.

Write out the Tridion tcm:Item XML.  See line 166 in the AddMetadataColumn.cs file.

xWriter.WriteAttributes(xReader, false);

Make sure the default tcm:Item attributes are added the the XML.

attrValue = GetMetadataValue(comp, "article_number");

 

Summary

Big thanks to the Tridion R&D team for giving us the power to do this.  It allows us to mold the Tridion CMS for our own organizations and improve efficiency while saving time.  This  can be very handy.  Programming the DataExtender is quite simple and most of our effort is in the Tridion API code itself – something we should all be familiar with. I am very happy with the approach and possibilities. We are very lucky that the base code has been provided from Seguei and we only need to get our config correct and deploy the files. One last warning that your code will be executed for every GetList request, so if your code is not fast then it is not for this type of extension. I can imagine for some use-cases going to the Tridion API for each piece of data will be too time-consuming and you may need to persist that data to an external system such as Redis that is much faster.  Have fun and try to remember to be kind.

Recently while updating an old VBScript Component Template I was surprised at seeing several bad practices in place and began to update the template. When we update a template we should always try to clean it, add comments, etc and leave it in a better state than we found it. Here I will give some tips for keeping things in order:

1. Good indentation – Tridion Templates open in their own browser window. Don’t edit the template there – DON’T DO IT! IT can be tempting, and maybe save you 30 seconds of copying it to your text editor – but you’ll screw up your indentation and make it impossible to follow for your other colleagues who are using text editors for template development.

Update: Set Notepad + to use spaces instead of tabs. Thanks Ingmar for the comment! http://stackoverflow.com/questions/455037/notepad-tabs-to-spaces

2. Access Component Presentations in Page Templates, Fields in Component Templates. Looping through Component Presentations in the Component Template is bad practice, slows the template down, ais not consistent with data model of Tridion and doesn’t make sense.

3. Use local variables in functions and subs. Using global variables in functions and sub-routines is bad practice. Respect the scope of a function or sub and pass in all variables, otherwise, it makes it very difficult to debug later.

4. Set variables when you declare them. For example, in VBScript use

Dim body : body = ""

5. Consistency with if conditions. Don’t write if statements on 1 line – especially within a nested if in VBScript. Makes it terribly difficult to follow.

I hope we can all establish good coding practices and follow them to keep our templates clean and in good health!

Tridion implements the Core Service using WCF 3.5 and this is the suggested interface to use for accessing the Tridion API outside of the Event System or Templates. Although I have heard horror stories of the configuration options and how difficult WCF is to work with, so far I did not have such experiences using the Core Service with ‘Add Service Reference’ in Tridion and then doing simple things. Today was different.

Today I tried making a small Console application using the config-less Core Service code from Jaime Santos’s post about the Data Extenders. This brought me into touch with the various bindings and security aspects – and I had a new respect and fear of WCF that I had not known before. Jaime uses the tcp binding and I understand this is the best performing binding. However, my Tridion Server declined the connection for TCP and I assume the port is blocked. Then, I tried modifying his code to use an HTTP Binding – but many of the options he uses are TCP only and I was quickly making a mess of the code. For some strange reason I tried using a WSHTTP binging – but that wants me to use https and the Tridion WCF Core Service does not accept it (or maybe my web server is not configured for it?). By this time I took another look at the Config-less Core Service Example from Puf and found new inspiration for making this work. His example requires a password for the account while the TCP example did not. I wonder why this is? I borrowed Puf’s code and adapted it to take advantage of some of Jaime’s ideas in his Handler. During the process I learned that the TCP binding is much better performing and we need to explicitly open and close the connections – the cost of opening a connection is very high.

Jaime uses the Core Service in a Data Extender where performance is  extremely important, so I understand why Jaime choses the TCP binding over HTTP.

In the end I have a small Console app that calls the Core Service using a BasicHttp binding. It gives us an easy way to get started testing Core Service code that later might end up in a GUI Extension or Import app.

Get the Code at https://github.com/rcurlette/TridionCoreServiceConsole

 

Adding a new column to the Listview in Tridion 2011 is easy with using Data Extenders. With a few simple config changes we can add the TCM URI column to our default view – no code needed! In this article I will explain how to add the URI column as well as give some debugging tips.

Concept

The Tridion Listview can be extended using DataExtenders. The idea is that the config file for the extender specifies an XPath expression and matches some data in the GUI Response.  We are lucky that the URI is part of the default GUI response and we simply need a little XSLT and config to show it.  However, we can also add new data to the GUI Response is by extending the DataExtender class in .NET.   I do not cover that here but there is an example in the Tridion LiveDocs.

We will:
1. Create a new GUI Extension – but only providing our config file since the URI is part of the default GUI Response
2. Add our new extension location to the System.config folder.

Steps for Adding the ListViewTCM Extension

1. Create a new folder under Tridion\Web\WebUI\Editors\ called ‘ListViewTcm’
2. Copy the extension config file below there. The important part in this code is the selector. It is doing an XPath match on the XML the Gui response is sending and selecting the ID attribute. We are lucky the ID is part of the response. Otherwise, we would extend the DataExtender class in .NET and is something I do not cover here.

Interesting part:

<ext:columns>
  <column xmlns="http://www.sdltridion.com/2009/GUI/extensions/List  "id="MyColumnID" type="data" title="URI"
  selector="@ID" translate="String" />
</ext:columns>

Whole config file:

3. Update the System.config to let it know about our new GUI “Extension”. 🙂

<editor name="ListViewTcm">
  <installpath>C:\Program Files (x86)\Tridion\web\WebUI\Editors\ListViewTcm\  </installpath>
  <configuration>ListViewTcm.config</configuration>
  <vdir/>
</editor>

4. Refresh the GUI and marvel at your new URI List column. I know I was delighted and surprised when I first saw it.

Debugging tips

Mostly useful when you start building your own DataExtenders to put more info in the Response. Not used here though, since URI is already part of our response:

1. Enable debug output for the GUI
System.config, <client debug=”true”>

2. Enable GUI debugging in the Browser
http://TridionDev2011/WebUI/Editors/CME/Views/Dashboard/Dashboard.aspx?mode=debug

3. View the Tridion.Web.Trace file in the WebUI/WebRoot folder. This is mostly used when debugging the .NET DataExtenders and something not covered here. Check if your extension is listed. This means the config is good and the GUI is trying to load it. When it does, you will see this message. This is good output: “Executing request-extension : ‘ListViewTcm'”. This is bad and most likely due to wrong Namespace / Class: “Configured dataextender ‘AddSchemaTitle, ListViewTcm’ can not be created, check .Net’s fusion log for more information”

Summary

Great job to the Tridion GUI Team for giving us the power over the Listview and ability to add columns. This extra information will save countless hours when searching for that extra piece of information and not having to open the item (or mouseover) to find it. The new column gets sorting and filtering for free and behaves just like the other columns. Overall I cannot wait to play more with this new and powerful feature.

GUI Extensions create a seamless experience for Tridion editors, providing shortcuts, new possibilities for bulk-editing and blueprint-aware tools that ease the task of daily content maintenance and improves the efficiency of all operations.  In this article I quickly create a new GUI Extension using the WhoDidIt example as the basis.  I also show how to convert an existing custom page, BCopy, to a new GUI Extension using the Core Service.  I hope after reading this you will be able to also convert your custom pages to GUI Extensions with little effort.

Getting Started
I use the previous GUI Extension tutorial, WhoDidIt?, as a basis for creating this new GUI Extension. There will be lots of find/replace action going on – so if this is your first time creating a GUI Extension then I strongly recommend you work your way through my previous article before continuing here. The first article explains the overall concepts and the relationships between pieces. Here I will focus on getting a GUI Extension created as soon as possible – re-using as many pieces from the GUI Extension WhoDidIt. In the first part I will walk through how to create the GUI Extension client using the previous solution of WhoDidIt as the basis on our BlueCopy extension. The second part focuses on the “Core” work – migrating the Tridion TOM API code to use the new Core Service.

What are BlueTools?
BlueTools are my Tridion Blueprint-focused tools to help you work with Tridion Blueprinted content.  This first extension in the BlueTools suite is ‘BlueCopy’, a modern interpretation on the BCopy custom ASP page. BCopy and this extension will do 1 thing and try to do it well – copy an item with its’ Blueprint children.

Downloads

WhoDidIt? GUI Extension Files– Start
BlueCopy GUI Extension Files – Solution- We create this below

Overview
Step 1: GUI Extension Client-Setup – Show a popup (~30 minutes)

Step 2:  GUI Extension Server Setup – Create the Web Service locally and call the Tridion Core Service (~15 mins)

Step 3:  Adding a new Web Service – Adding our new Service to ServiceStack (~15 mins)

Step 4:  Deploy the GUI Extension Web Service to our CMS (~20 mins)

Step 5:  Wire the GUI Extension client to the Web Service on the Server

Step 6: Converting ASP TOM API code to Tridion 2011 Core Service code

Step 7:  Finishing up – polishing the GUI and adding another field

Step 1: GUI Extension Client-Setup – Show a popup (~30 minutes)

1. Get the GUI Extension WhoDidIt files from GitHub, https://github.com/rcurlette/WhoDidIt/downloads. Extract it and rename the files from WhoDidIt to our extension name. Rename the folder from ‘rcurlette-WhoDidIt-552c9a2’ to ‘BlueTools’ or your GUI Extension.

2. Rename the files 

  • WhoDidItCmd.js ->GuiExtensions\BlueTools\GuiExtension\client\js\BlueToolsCmd.js
  • WhoDidIt.css -> GuiExtensions\BlueTools\GuiExtension\client\css\BlueTools.css
  • WhoDidIt.config -> GuiExtensions\BlueTools.config

3. Update files.  Find / replace WhoDidIt and Sherlock with your tool name.

  • BlueTools.config -> Find / replace ‘Sherlock’ with BlueTools and ‘WhoDidIt’ with BlueCopy. Replace the text ‘Who Did It?’ with what text you want shown on the right-click, for example, ‘BlueCopy’.
  • Open the GUI Extension BlueToolsCmd.js file.  Find / Replace Sherlock with BlueTools and WhoDidIt with BlueCopy. Save.

4. Update the GUI Extension action – Open the BlueToolsCmd.js file, change the _execute method  alert message to ‘alert(‘Really Excellent!’);’ and comment out all lines starting with ‘var selectedID’ until popup.open();.  This update will show only the popup and comment out all other lines, not worrying about other client HTML / JS files.

5.  Update BlueTools.config file and add an assignid value.  Whenever you change the config file the GUI flushes the cache automagically.  Nice!  🙂

old:

<ext:extension name="BlueToolsExtension" assignid="" insertbefore="cm_refresh">

new:

<ext:extension name="BlueToolsExtension" assignid="BlueCopy" insertbefore="cm_refresh">

 

6. Deploy to Server – On the CMS server, create a new folder under ‘C:\Program Files (x86)\Tridion\web\WebUI\Editors’ for the new GUI Extension. I create a folder called BlueTools. Copy all the files in the GuiExtensions\BlueTools\GuiExtension folder to the server at ‘C:\Program Files (x86)\Tridion\web\WebUI\Editors\BlueTools’.  Then you should have BlueTools\GuiExtension.

7. Create IIS Virtual Dir –  Open IIS on CMS Server, Go To Web Sites, SDL Tridion 2011, WebUI, Editors. Right-click, select create a new IIS Virtual Directory to host the GUI Extension, name it BlueTools, and browse to the folder you copied the BlueTools GUI Extension to on the server.

8. Edit the System.Config located at ‘c:\Program Files (x86)\Tridion\web\WebUI\WebRoot\Configuration\System.Config‘, and let Tridion know about our new extension.

&lt;editor name="BlueCopy"&gt;
  &lt;installpath&gt;C:\Program Files (x86)\Tridion\web\WebUI\Editors\BlueTools\GuiExtension\&lt;/installpath&gt;
  &lt;configuration&gt;<strong>BlueTools</strong>.config&lt;/configuration&gt;
  &lt;vdir&gt;BlueTools&lt;/vdir&gt;
&lt;/editor&gt;

9. Flush browser cache.  (Ctrl-shift-delete in Chrome) Refresh the GUI, see our new option in the right-click context menu. Since WhoDidIt did not include a ribbon extension you will not have a ribbon button here.

10. Test the GUI Extension client using the right-click menu option ‘BlueCopy’. You should see an alert with the text ‘Really Excellent!’

*Note: On my first test I saw the text in the menu but clicking on it did not do anything! Opening up the Javascript console in Chrome (F12 key) and in the Console window I see this message: “Command ‘BlueToolsCommand’ is not registered“. To fix this I needed to add an ID to the <ext:extension line. Maybe this is because I have more than 1 GUI Extension registered and also inserting at the same position? For more info about all the values in the config file have a look at my previous post, http://www.curlette.com/?p=279.

old:

<ext:extension name="BlueToolsExtension" assignid="" insertbefore="cm_refresh">

new:

<ext:extension name="BlueToolsExtension" assignid="BlueTools" insertbefore="cm_refresh">

Review:

We did it! We have successfully added another GUI Extension Client. It doesn’t do much yet – no Tridion API magic.  Now time to create the server-side web service to do something with the Tridion API and call our popup from the _execute method.  If you have any problems – review the config file and my debug article.

Step 2:  GUI Extension Server Setup – Create the Web Service locally and call the Tridion Core Service (~15 mins)

1. Get the Tridion2011ServiceStack project from GitHub

2. Add your CMS Url.  Open the solution in Visual Studio 2010 SP1. Do a find / replace for TridionDev2011 with your CMS server name for all files in Solution. Change the look-in option to ‘Entire solution. You should have 53 replacements. Save all.

3. Update the web.config file and put in your CMS server URL, Username, and password

4. Add the Tridion Core Service DLL reference to the solution. Get it from your Tridion server in the /bin/client folder. Copy it locally before adding it – don’t add it from a network share. It is not included in the ServiceStack project because it is a Tridion file.

5. Step through code.  Build. Hit F5 (play) to Debug solution – the test page for WhoDidIt appears.  Note the url and port # visual studio uses in your browser. If your URL is not ‘http://localhost:61860’ then copy it and update the page ‘GetTridionItem.htm’, replacing the URL with your localhost URL. Otherwise, hit the ‘Go’ button and you should hit the breakpoint ‘ return Repository.GetByUri(request.Uri);’ in the OnGet method of our ‘TridionItemRepository’.

Review

Developing locally is great –  we can write our Tridion API code in the Service, develop and debug our Core Service Tridion API calls directly in our Visual Studio development environment.  Our test page calls the Tridion2011ServiceStack Web Service via AJAX and we hit our Service method calling the Tridion API and can step through the code. So far, so good.

Step 3:  Adding a new Web Service – Adding our new Service to ServiceStack (~15 mins)

1. Create a new model class. Right-click on the models folder, select ‘Add new class’.  Add any properties to the class you want to return to the js client. You can also copy/paste the properties from the TridionItem class. I am creating a ‘BlueCopyItem’ model and adding 3 properties I know I’ll need – Title, URI, and Error (to hold the error message to pass back to the js client).

namespace Tridion2011ServiceStack.Models
{
    public class BlueCopyItem
    {
        public string Title { get; set; }
        public string Uri { get; set; }
        public string Error { get; set; }
    }
}

2. Create a new Service class. This handles the request from the GUI Extension client.  Right-click on the Services folder, select ‘Add new class’. I called mine ‘TridionBlueCopyService.cs’. Copy / paste the code from TridionItemService. Replace ‘TridionItem’ with the name of your model class from step 1, ‘BlueCopyItem’. Don’t forget to change the ‘Look in’ option to ‘Current document’ and not ‘Entire Solution’.   You should have 4 replacements.  Also, select the match case – otherwise it will rename both your Class name (TridionItem) and the variable names (tridionItem).

Remember to inherit from the RestServiceBase class.

: RestServiceBase<BlueCopyItem>

3. Create a new Repository class. Right-click on the Repositories folder, select ‘Add new class’. I called mine ‘BlueCopyRepository’.  Copy / Paste code from TridionItemRepository.  Update the code – rename ‘TridionItemRepository’ with ‘YourModelClassRepository’. Replace ‘TridionItem’ with ‘BlueCopyItem’ and ‘tridionItem’ with ‘blueCopyItem’.  Remove the line 24 adding the LastModifiedProperty since we do not have it in our model. // remove -> tridionBlueCopy.LastModifiedBy = versionInfo.Revisor.Title;

4. Add our service mapping – Open AppHost.cs and add 1 more line below ‘.Add<TridionItem>(“/tridionItem”)’, where TridionItem is your model class name and /tridionItem is the URL you want to use for your web service. This can be whatever you want.  I added this:

.Add<BlueCopyItem>("/blueCopy");

5. Register our repository – In AppHost.cs register your new Repository. Add a new line below ‘container.Register(new TridionItemRepository());’ on line 62 and change TridionItemRepository with your repository name.

container.Register(new BlueCopyRepository());

6. Build, add namespaces as needed.

7. All done! No config needed. 🙂 Service Stack is built using the convention over configuration principle and steps 4 and 5 are all we need to do to make our new service visible to Service Stack.

8. Make a new test page. Copy / paste GetTridionItem.htm and give it a new name such as ‘BlueCopyTest.htm’

9. Put the new Web Service URL in the test htm page from the AppHost.cs file (ie’ /tridionBlueCopy). Replace ‘http://localhost:61860/api/tridionItem’ with ‘http://localhost:61860/api/yourServiceUrl’ located in the test htm file.  Mine is: ‘url: “http://localhost:61860/api/blueCopy”,’

10. Set the new test page as your default start page by right-clicking on it and choose ‘Set as Start Page’.

11. Add a breakpoint to the new Repository class (ie. BlueCopyRespository.cs), line ‘CoreServiceClient client = new CoreServiceClient();’ (assuming you did not change anything in the Repository code yet).

12. Hit play, run in debug mode. Your new htm test page should show up. Push the ‘Go’ button and your new breakpoint should be hit. Step through the Repository OnGet code, and at the end your page will show the Title and URI of the item. Congratulations – you are now ready to add your new code.

Note: If you get a popup and the text looks like jquery2189072189723131 then you’ve got an error in the Repository Tridion code. 🙂 Time to debug the server. I decided to place another breakpoint on the OnGet method in the BlueCopyService class. This is called first. I got into the debugger this time, but when pressing F11 to step into the repostitory I got a popup from Visual Studio saying it could not find my repository. I forgot to add it to the AppHost Repositories Funq container! To sovle this I opened the AppHost.cs file, and added

container.Register(new TridionBlueCopyRepository());

Review

It’s fun to add a new web service so easily and then step through the code in Visual Studio with the debugger.  Creating a web service and configuring it for test is not an easy step – there are several files involved and many places we find/replace the original class names with our new model class. No worries – once this is done you will not need to do it again for this web service. Also, I find it fairly straight-forward and if you follow the steps above you should have your new web service ready to go in no time. One advantage to this approach is you can call your new web service from any client, an HTML test page as shown above, your new GUI Extension, or a classic ASP custom page!

 

Step 4:  Deploy the GUI Extension Web Service to our CMS (~20 mins)

1. Copy Tridion2011ServiceStack files to server, usually I create a new folder under InetPub\wwwroot.  I called mine ‘BlueToolsWebService’.  I deploy my MVC website using the ‘Publish’ option of Visual Studio.  I have a drive mapped to my server and then choose ‘File System’ in the publish method listbox.  Then, in IIS, create a website to host the ServiceStack MVC Web Service (or host the Web Service as a Windows Service or Console App).  Don’t forget to change the App Pool to a .NET 4.0 App Pool or it will not run.  You may also want to run it on a different port.  In my previous post  I describe this in more detail.

2.  Test the Web Service.  Open a web browser and go to the URL of your web service.  You should see a reply from ServiceStack like this one:  “Snapshot of BlueCopyItem generated by ServiceStack”. This works! Open the popup.js file on our local dev box and chage the URL to the website URL from the browser.

3.  Debug the results in Chrome.  Now hit F5 and run the test page again in Visual Studio.  Use the Developer Tools (F12 key) and go to the Scripts tab.  Select the BlueCopyTest.htm file in the dropdown and set a breakpoint on the line with ‘ $(“#feedback”).html(“<div class=’successMessage’>Title’.  Now, hit ‘Go’ and our breakpoint will be hit.  then mouseover the data object in the line with ‘success: function (data) {‘ and see the title and uri properties.  You can also highlight ‘data’, right-click, and select ‘Add Watch’ to see the values.

 

Step 5:  Wire the GUI Extension client to the Web Service on the Server

1. Enable the popup.  Open the GUI Extension .js file with the _execute method. Mine is called ‘BlueToolsCmd.js’.  The location on the server might be something like: C:\Program Files (x86)\Tridion\web\WebUI\Editors\BlueTools\client\js\BlueToolsCmd.js UnComment the lines starting with ‘selectedId’ and ending with ‘popup.open’

2.  Add text to popup.htm.  The original WhoDidIt popup only had an AJAX call and does not display anything.  Let’s add ‘Hello World’ text to the popup.htm so we have something to see when we call it.

3. Confirm the url to the popup is working. Popup on server might be located at ‘WebUI\Editors\BlueTools\client\html’. Test this in the browser.  If your CMS URL is http://TridionDev2011 then the test URL would be http://TridionDev2011/WebUI/Editors/BlueTools/client/html/popup.htm.

4.  Test in CMS.  Now let’s clear our browser cache and try our GUI Extension again.  If you see the popup- congrats! If not, double-check your .js file _execute command, make sure it is deployed, and refresh your browser.  Also, open up the Chrome debugger or FireBug and see my post about debugging.

3. Add web service URL to popup.js. Our client is now 100% working and also showing the popup. Let’s call our new Web Service.  Change the URL property in the popup.js file located in the C:\Program Files (x86)\Tridion\web\WebUI\Editors\BlueTools\client\html\js folder to point to our new web service and try again. This assumes you have not changed any code in the repository class.

You will see the URL as url:  “‘http://TridionDev2011:8001/Tridion2011ServiceStack/api/tridionItem”. Change TridionDev2011 to your CMS URL and ‘tridionItem’ to your new Web Service URL (should look like your test htm page). My new url is ‘http://TridionDev2011:8001/BlueToolsWebService/api/blueCopy’  Note: Clear browser cache again and refresh.

4. Test in GUI, Debug the data returned in Chrome.

– Run GUI Extension

– When popup is open, load the Chrome Developer Tools (F12), scripts window, select popup.js.

– Put breakpoint on line $(“#suspect”).text(data.lastModifiedBy);

– Reload popup html window, breakpoint is hit.

– Minimize call stack window on right, open Scope variables

– Open Closure, data and see your properties here. This is your model object returned from ServiceStack. Notice your propery names are camelCased.

Review

This is the most critical part – where we see our 2 major pieces talking to each other. Give yourself a pat on the back – you’ve made it through the most difficult part.  Now we’re ready to move onto the real work – using the Tridion API to do something. Right now all the pieces are in place, working, and we’re finally ready!  The 5 pieces are:  Extension Config -> .js (with _execute) -> popup.htm -> popup.js -> Web Service -> Tridion Core Service

Step 6: Converting ASP TOM API code to Tridion 2011 Core Service code

Tridion introduced the Core Service in Tridion 2011 and announced this is the official way to talk with the Tridion API outside of the Event System or Templates.  We will use the Core Service to talk to the API and do the work.  Our AJAX call is passing the Component URI to the Web Service.

I do this all locally and use a local htm test page with the javascript / css locally as well.  Just as we did in Step 2 above – we will set a breakpoint in our Repository class and step through the Tridion Core Service Code.

In this example I need to port my existing classic ASP Custom Page code for copying an item and BluePrint children to the new Core Service.  Below are some highlights and points about the differences.

The Core Service code has 3 main parts:

1. Create a new copy of the Parent source item (Page or Component)
2. Get Localized Versions of the source item
3. For each Localized Version, localize the new copy and update the contents with the localized source item.

1.  Copy the Parent Tridion Object

TOM API:

Function CreateNewItemCopy(organizationalItemUri, itemType, title, xml, directory, filename)
	'response.write "create new item" & organizationalItemUri & "," & itemType & "," & title & "," & xml & "," & directory & "," & filename
	Dim newItem : set newItem = tdse.GetNewObject(itemType, organizationalItemUri)
	newItem.UpdateXml(xml)
	newItem.Title = title

	if(itemType = 64) then ' page
		newItem.FileName = filename
	elseif(itemType = 4) then ' sg
		newItem.Directory = directory
	end if

	newItem.save(true)
	CreateNewItemCopy = newItem.id
	set newItem = nothing
End Function

Core Service API: *Note: I did change the fundamental approach here since I now use the copy method

private string CreateNewItemCopy(string title, RepositoryLocalObjectData source, string filename)
        {
            string newItemUri = "";
            try
            {
                ItemType tridionItemType = GetTridionItemType(source);
                string orgItemUri = source.LocationInfo.OrganizationalItem.IdRef;
                var newItem = client.Copy(source.Id, orgItemUri, true, new ReadOptions());
                newItem.Title = title;
                if (tridionItemType == ItemType.Page)
                {
                    PageData pageData = newItem as PageData;
                    pageData.FileName = filename;
                    client.Update(pageData, new ReadOptions());
                }
                else
                {
                    client.Update(newItem, new ReadOptions());
                }
                newItemUri = newItem.Id;
            }
            catch (Exception ex)
            {
                throw;
            }

            return newItemUri;
        }

Highlights / Differences:

The Tridion TOM provides us a nice easy way to create content in any type of item – UpdateXml.  The Tridion Core Service does not have this method but instead uses an Update method on the Web Service client.  I posted a question on StackOverflow about this and got a great response – not only was my answer provided, but a new suggestion of using the Copy method of the API instead of UpdateXML. 🙂

Copy item – also passing in Read options to make sure I can read data later.

' VBScript / TOM
Dim newItem : set newItem = tdse.GetNewObject(itemType, organizationalItemUri)
newItem.UpdateXml(xml)
// Tridion CORE Service / .NET
var newItem = client.Copy(source.Id, orgItemUri, true, new ReadOptions());

2. Get Localized Versions

I really like the new approach with BluePrintChainFilterData instead of GetListUsingItems (from TOM) to get the localized child elements. The Core Service and the filter BluePrintChainFilterData also returns the Parent item – so watch out – you may need to filter it out as I do here. Big thanks to user978511 on StackOverflow for the help and example code http://stackoverflow.com/questions/9515647/getlistusingitems-with-tridion-core-service-returns-more-items-than-tom.

TOM API:

Function GetLocalizedItemNodes(itemUri)
	Dim tridionItem : set tridionItem = tdse.GetObject(itemUri,1)
	Dim rowFilter : set rowFilter = tdse.CreateListRowFilter()
	call rowFilter.SetCondition("ItemType", GetItemType(itemUri))
	call rowFilter.SetCondition("InclLocalCopies", true)
	Dim usingItemsXml : usingItemsXml = tridionItem.Info.GetListUsingItems(1919, rowFilter)

	Dim domDoc : set domDoc = GetNewDOMDocument()
	domDoc.LoadXml(usingItemsXml)
	Dim nodeList : set nodeList = domDoc.SelectNodes("/tcm:ListUsingItems/tcm:Item[@CommentToken='LocalCopy']")

	set tridionItem = nothing
	set domDoc = nothing
	set GetLocalizedItemNodes = nodeList
End Function

Core Service API:

private XContainer GetLocalizedItems(string itemUri)
        {
            XContainer localizedItems = null;
            try
            {
                BluePrintChainFilterData filter = new BluePrintChainFilterData();
                filter.Direction = BluePrintChainDirection.Down;
                localizedItems = client.GetListXml(itemUri, filter);
            }
            catch (Exception ex)
            {
                throw;
            }
            return localizedItems;
        }

3.  Update Localized Items

Some lines in the code commented out with //… – there is code there – please see the source code on GitHub.

TOM API:

Sub UpdateLocalizedItem(itemXml, itemUri, pubUri, filename, directory)
	Dim newTridionCopy : set newTridionCopy = tdse.getObject(itemUri,1, pubUri)
//...
	if(newTridionCopy.Info.IsLocalized = false) then
		newTridionCopy.Localize
	end if
	//...code removed...
	newTridionCopy.UpdateXml(itemXml)
	newTridionCopy.Title = newParentTitle

	' set sg and page props
	if(itemType = 64) then ' page
		newTridionCopy.FileName = filename
	elseif(itemType = 4) then ' sg
		newTridionCopy.Directory = directory
	end if

	newTridionCopy.Save(true)
// ...
End Sub

Core Service API:

private string UpdateLocalizedItem(string title, string uriLocalizedSource, string newItemUri)
{
	try
	{
		ItemType tridionItemType = GetTridionItemType(uriLocalizedSource);
		var newItem = client.Read(newItemUri, new ReadOptions()) as RepositoryLocalObjectData;
		var oldItem = client.Read(uriLocalizedSource, new ReadOptions());
		if (newItem.MetadataSchema != null)
		{
			var newItemMetadataSchema = client.Read(newItem.MetadataSchema.IdRef, new ReadOptions()) as SchemaData;
			newItem.Metadata = GetMetadata(newItem.Metadata, newItemMetadataSchema.NamespaceUri);
		}

		if (tridionItemType == ItemType.Page)
		{
			PageData newPage = newItem as PageData;
			PageData oldPage = oldItem as PageData;

			newPage.ComponentPresentations = oldPage.ComponentPresentations;
			newPage.Title = title;
			client.Update(newPage, new ReadOptions());
			return newPage.Id;
		}
		else if (tridionItemType == ItemType.Component)
		{
			ComponentData newComp = newItem as ComponentData;
			ComponentData oldComp = oldItem as ComponentData;

			newComp.Schema = oldComp.Schema;
			newComp.MetadataSchema = oldComp.MetadataSchema;
			newComp.Content = oldComp.Content;
			newComp.Metadata = oldComp.Metadata;
			newComp.Title = title;
			return newComp.Id;
		}
	}
	catch (Exception ex)
	{
		log.Error(ex.Source + "," + ex.Message + "," + ex.ToString());
		log.Error(ex);
	}
	return uriLocalizedSource;
}

 

Review:  Creating the Core Service code is where we get to talk with the Tridion API and some familiar old school objects and names come back into the light.  I usually do all my Core Service development locally, including debug, and only use the Server log files to know that something went wrong.  No more fighting with other developers over remote Windows Server sessions! 🙂  I cannot express enough how nice this is – and how it speeds up the development / debug / deploy cycle.  While there is a lot more to know about the Core Service to build good solutions – the community and documentation examples provided the help I needed.  Please grab the source code from GitHub for all the details.

 

Step 7:  Finishing up – polishing the GUI and adding another field

GUI Extensions are by nature a client-side HTML / JavaScript / jQuery application making Web Service calls.  The better your client side dev skills are – the better your GUI Extension will feel.  Unfortunately, most Tridion developers have spent little time doing client development (me included!) and this is a time consuming part of writing a GUI Extension.  We can no longer avoid JavaScript!

Adding new properties – SourceTitle and Filename

Currently we only return the title of the new copied item- but what if we wanted to also return the title of the original item?  No problem.

1.  Add the property to the Model

public class TridionCopyItem
    {
        public string Title { get; set; }
        public string SourceTitle { get; set; }
        public string Filename { get; set; }

2.  Set the property value

// Create the Response Object to send back via Ajax to our GUI Client
TridionCopyItem tridionItem = new TridionCopyItem()
{
	Title = GetCmsEditUrl(newItemUri, title),
	Uri = newItemUri,
	SourceTitle = itemToCopy.Attribute("Title").Value,
        Filename = filename
 };

3.  Read the value in the js client

success: function (data) {
$(document).ready(function () {
	/* Render the template with the movies data */
	$.each(data, function () {
		$('<div>' + this.sourceTitle + '<span class="icon-arrow-right" style="padding-right:4px;padding-left:4px;"></span>' + this.title + '</div>').appendTo("#componentList");
	});

 

Adding input fields to HTML form for the Title

Adding another input field is no problem.  We simply add it to our HTML form and it automatically gets serialized by jQuery into JSON and sent via AJAX to our ServiceStack Web Service.  In the Web Service our Model should have a Property with the same name, and our Service class needs an input parameter with the same name.

Look and feel

I use the Twitter Bootstrap CSS framework for all custom pages and GUI Extensions.  This time I also decided to adopt the Golden Ratio for the popup size.  Overall I am very happy with the design and the look.

I added more jQuery and HTML to the final form but did not change the Web Service.  Maybe this is typical for a GUI Extension to first get the Core Service working and then spend lots of time polishing the jQuery and HTML interaction with the user. Consider the skillset for your extensions – find your local JavaScript / jQuery expert and get them involved in the GUI Extension project.

Summary:

Building GUI Extensions involves 3 main activities.  First, get the menu option in the GUI and a popup saying ‘Hello’.   Second, setup the Web Service.  Third, get the Core Service doing the magic bits with the Tridion API.  The first step, with the GUI, involves a lot of server-side work of updating config files, deploying js files, and working with IIS.   However, the Tridion API work with the Core Service feels completely different – doing the work in the Visual Studio development tool and being able to iterate quickly.  Overall I like the separation of concerns – it just needs to become clear in our minds what happens where and we can easily design nice GUI Extensions without too much work.

I hope you managed to follow along and feel more comfortable about creating GUI Extensions.  Now time to start coding the next one…  🙂

Downloads

WhoDidIt? GUI Extension Files– Start
BlueCopy GUI Extension Files – Solution

Workflow Dreamin’

March 23rd, 2012 | Posted by Robert Curlette in Tridion - (0 Comments)

Workflow has been in the Tridion product since version 4 and is one of the most often requested and least often implemented or used features in the product.  The upcoming bundles feature in the product has sparked up some discussions this week.  In this article I will attempt to de-construct these old assumptions and provide my dreams for workflow.

Overview

Chris Summers and Dominic Cronin brought us great posts this week about Tridion Workflow and I agree with both Dominic and Chris’s points. However, they suggest we might be missing a method in the API or the business needs to think and plan more for the workflow. But, I disagree. Tridion has added API features since it was built but the concept has remained the same since v4. I would argue that the assumptions that workflow was built for in 2001 do not exist today. These ficticious organizations and ficticious Authors are not there and therefore the workflow system we have does not work. Current Tridion workflow assumptions are reviewed below:

Assumption: Authors need everything reviewed all the time.

Tridion workflow currently assumes that the we have some rogue users in our Tridion system that need every change they make reviewed and approved every time for all content using a particular schema, for example an Article, with no exceptions. These rogue users are believed to be dangerous and we need to watch everything they do otherwise our site will fall to ruins.
In reality, this is very far from the truth. Our Tridion users are regular Tridion users, most of them daily users. They are experts in the content and know it better than most people in the organization. They know when they need approvals or not – and want to do the right thing by requesting an approval for a completely new article. However, for correcting a typo in some content they will not need to have an approval since it is a small change. In other words – we need to empower our Authors and make it very easy for them to get approval when they need it or want it, and all other times stay out of the way.

Assumption: Content needs approval every time.

Currently Tridion workflow is assigned to a Schema and whenever any content using that Schema is modified, even a little bit, the content goes into workflow and needs approval. A lot of content updates are for fixing typos, updating an image, adding 1 line to the text. What we need an approval for is big content changes and new items. And, who knows when we need an approval? – the Author. Because we trust them to know their content.

Assumption: All content using this Schema should go into workflow.

Context matters. Today when workflow is assigned to a Schema, all content using that Schema goes into workflow when edited. Maybe we want to have all articles under the Press Release folder to go into workflow A and the articles in the News folder into Workflow B. Or maybe the articles in the News folder don’t go into workflow, because the Author is the person in the organization responsible for that content – and we trust them to make the right decisions. We don’t want to turn workflow off for this – we want to specify the context the workflow is turned on.

Assumption: Workflow will protect our content by allowing certain roles to make changes

If we want to restrict users – then use the excellent Tridion security system with roles and rights. Want to not allow an author to change content? Set the security rights. Limit publishing to Staging? No problem, restrict publishing. But, I argue, limiting what users can do is a matter of security, not workflow.

Solution – Dreams for a New Workflow- If This Then That

My proposal is to flip workflow on its’ head. Think of the app If This Then That. The idea is simple – If ‘This’ happens then do ‘That’. Here are some examples, http://ifttt.com/recipes, Imagine these Tridion recipes:

If ‘I’ create an ‘article’ AND
If the ‘article’ is in the folder ‘News’ AND
If the date is ‘today’ THEN
Mark it for ‘Needs Approval’ AND
Notify ‘John’ with ‘once per day’ by ‘Email’

Or, maybe a pull workflow created by ‘John’ the manager:

If ‘anyone’ creates an ‘article’ AND
If the ‘article’ is in the folder ‘News’ THEN
Notify ‘me’ ‘once per day’ by ‘Email’

Or, maybe a pull workflow created by ‘Sally’ the web products manager:

If ‘anyone’ publishes ‘anything’ to ‘live’ AND
If the ‘article’ is in the structure group ‘products’ THEN
Notify ‘me’ ‘each time’ by ‘workflow notification center’

The power of this is amazing. We can create flexible rules that follow our organic organizations. And, we can empower our users.

Summary

The core concept here is notifications – notify someone that something has happened and request an approval. That’s it.  My dream is to allow users to pull and push notifications for actions in the GUI. I believe Tridion users and not only editors, but are owners of content, and responsible for the content they publish online. I believe they want to do the right thing – and we should help them do that with a flexible rules system that includes notifications and approvals.

Thoughts? Please share.

If you’re like me and you work in a lot of legacy Tridion implementations then there’s a good chance you spend time each week hunting for that 1 line of code to update, burried somewhere in the thousands of lines of VBScript templates. Oh, you don’t have this problem, lucky you! 🙂

Show Template Source Custom Page

I created a very small custom page that loops through 1 folder and displays all the template code to the screen. My folder structure specifies 1 template type per folder and the custom page is setup that way, and it loops through child folders too.  Also – you can pass in any URI of a folder and get back the source. You need to use ‘View Source’ in your browser and then copy the source to your favorite text editor. One tip – wait until the whole page finishes loading – otherwise you’ll not see all the templates.

*Update:  Pages also supported – thanks to Mihai’s suggestion in the comments.

Code highlights

' Show Template
Response.Write template.Content

The Code is hosted on GitHub as a Gist, https://gist.github.com/2044332 and also embedded here:

Enjoy!

Working with Tridion 2011 is a breath of fresh air – and sometimes from unexpected places. Today I was working with the new Content Delivery API and had a refreshingly light feeling.  I was immediately comfortable with the Criteria objects and the new possibilities. It just feels right.

Tridion Broker Query – Get item based on Metadata from all Publications

In Tridion 5.3 I have a query that finds a product based on the SKU in the Metadata for 1 Publication.  Now I have a need to get all instances of the Component for all Publications.  Using Tridion 5.3 we solved this for 1 Publication with a SearchQuery like the one below, but I could not find a way in Tridion 5.3  to query the Broker and all Publications for 1 Component using a Metadata query.  Using Tridion 2011 for getting all published instances, regardless of Publication, is very easy.

Tridion 5.3 Broker Query:

<%@ page import="com.tridion.dcp.*"%>
<%@ page import="com.tridion.dcp.filters.*"%>
<%@ page import="com.tridion.dcp.filters.query.*"%>
<%@ page import="com.tridion.broker.components.meta.*"%>
<%@ page import="com.tridion.util.*"%>
<%@ page import="com.tridion.meta.*"%>
<%
SearchFilter filt = new SearchFilter(publicationURI);
Query q = new Query();
String[] results = null;
strCustomQuery = "(KEY_NAME = '" + key + "' AND KEY_STRING_VALUE = '" + value + "')");
results = filt.match(q.toString(), strCustomQuery, ComponentMetaHome.FIELD_CREATION_DATE + "=asc", 100);

ComponentPresentationFactory cpFactory = new ComponentPresentationFactory(publicationURI);
ComponentMetaFactory componentMetaFactory = new ComponentMetaFactory(publicationURI);
if (results != null) {
	int i = 0;
	for (String result : results) {
		tcmURI = new TCMURI(result);
		ComponentPresentation cp = cpFactory.getComponentPresentationWithLowestPriority(tcmURI.getItemId());
		out.println(cp.getContent());
	}
}
%>

Tridion 2011, All Publications:

<%@ page language="java" contentType="texthtml; charset=UTF-8"%>
<%@page import="com.tridion.broker.StorageException,
com.tridion.broker.querying.*,
com.tridion.broker.querying.criteria.*,
com.tridion.broker.querying.criteria.categorization.*,
com.tridion.broker.querying.criteria.content.*,
com.tridion.broker.querying.criteria.metadata.*,
com.tridion.broker.querying.criteria.operators.*,
com.tridion.broker.querying.criteria.taxonomy.*,
com.tridion.broker.querying.filter.LimitFilter,
com.tridion.broker.querying.sorting.SortParameter"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
     <head>
          <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
          <title></title>
     </head>
<%
String sku = "1EX0.031.03";
String fieldName = "article_number";

//Create query
Query myQuery = new Query();

Criteria myCriteria = null;
CustomMetaKeyCriteria metaField = new CustomMetaKeyCriteria(fieldName);
CustomMetaValueCriteria customMeta = new CustomMetaValueCriteria(sku);

// glue the metadata together
AndCriteria fieldCriteria = new AndCriteria(customMeta, metaField);
myCriteria = fieldCriteria;
myQuery.setCriteria(myCriteria);

// Sort it
SortParameter sortParameter = new SortParameter(SortParameter.ITEMS_TITLE, SortParameter.ASCENDING);
myQuery.addSorting(sortParameter);

// Get results
myQuery.setResultFilter(new LimitFilter(100));

// Display
String[] itemURIs = myQuery.executeQuery();
String strOutput = "";
for (int i = 0; i < itemURIs.length; i++) {
	strOutput += itemURIs[i] + ", ";
}
%>
<body>
	output = <%=strOutput%>
</body>
</html>

Tridion 2011, 1 Publication (Code borrowed from Tridion Live Documentation with slight modifications)

<%@ page language="java" contentType="texthtml; charset=UTF-8"%>
<%@page import="com.tridion.broker.StorageException,
com.tridion.broker.querying.*,
com.tridion.broker.querying.criteria.*,
com.tridion.broker.querying.criteria.categorization.*,
com.tridion.broker.querying.criteria.content.*,
com.tridion.broker.querying.criteria.metadata.*,
com.tridion.broker.querying.criteria.operators.*,
com.tridion.broker.querying.criteria.taxonomy.*,
com.tridion.broker.querying.filter.LimitFilter,
com.tridion.broker.querying.sorting.SortParameter"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
     <head>
          <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
          <title></title>
     </head>
<%
int iPublicationID = 129;
String sku = "1EX0.031.03";
String fieldName = "article_number";

//Create query
Query myQuery = new Query();

Criteria myCriteria = null;
PublicationCriteria pubCriteria = new PublicationCriteria(iPublicationID);
CustomMetaKeyCriteria metaField = new CustomMetaKeyCriteria(fieldName);
CustomMetaValueCriteria customMeta = new CustomMetaValueCriteria(sku);

// glue the metadata together
AndCriteria fieldCriteria = new AndCriteria(customMeta, metaField);
AndCriteria allCriteria = new AndCriteria(fieldCriteria, pubCriteria);
myCriteria = allCriteria;
myQuery.setCriteria(myCriteria);

// Sort it
SortParameter sortParameter = new SortParameter(SortParameter.ITEMS_TITLE, SortParameter.ASCENDING);
myQuery.addSorting(sortParameter);

// Get results
myQuery.setResultFilter(new LimitFilter(100));

// Display
String[] itemURIs = myQuery.executeQuery();
String strOutput = "";
for (int i = 0; i < itemURIs.length; i++) {
	strOutput += itemURIs[i] + ", ";
}%>
<body>
	output = <%=strOutput%>
</body>
</html>

Helpful links:

Summary:

Tridion has opened a can of awesome sauce and spread it liberally over the new Tridion 2011 Content Delivery API. Marvel at the amount of Criteria available and prepare for the new possibilities it provides. Definitely worth a serious look when you upgrade – don’t leave your SearchFilter queries in there because you can – do yourself a favor and migrate that code right now to use the new API.

The Tridion TOM.NET API and the Core Service introduced some new ItemTypes to Tridion, including the IdentifiableObject type.  While this is not a UFO, it might appear to you as an Unidentifable (Flying) Object.  In this post I hope to explain these other ItemTypes: IdentifiableObject, SystemWideObject, RepositoryLocalObject, and VersionedItem and are what I call the Tridion UFOs.

The Tridion Core Service methods return the IdentifiableObject method a lot and knowing more about this type and also how to cast it to a more specific type is very important to get a working solution.  Recently I struggled a bit with this and was fortunate to have the Tridion Community on StackOverflow help me with my question.

In the old TOM API we did not have any generic parents to the objects – we always got back a concrete instance of an object that matched 1-1 to something we could see and touch in the GUI. This is not the case with TOM.NET or the Core Service where we can get back base-objects that contain a subset of the methods and properties of our real objects. These new classes are super-handy in .NET where we can use Generics and create more re-usable methods. But, using them requires a little bit of knowledge as to which one is best for the given scenario.

The Tridion 2011 UFOs

IdentifiableObject

The opposite of a UFO, but the most prevalent ItemType is the IdentifiableObject (IO). The favorite return type of many methods in TOM.NET and the Core Service, the IdentifiableObject always leaves us wanting more. Which is why we usually cast it to a more concrete object as soon as possible – unless we really only need the Title or URI.

Methods and Properties available:  * means the property is mandatory.

  • Title*
  • ID
  • IsEditable
  • AllowedActions
  • ExtensionData

SystemWideObject

Items not within a Publication. Most of these can be found in the Administration section of the GUI. Examples are Users and PublicationTargets.

Methods and Properties available:

  • Same as IdentifiableObject

RepositoryLocalObject

Repository is another name for Publication (why couldn’t they call it PublicationLocalObject?) so this means all items within our Publication. This ItemType is a good generic one to use when dealing with most content types.

Methods and Properties available include all from IdentifiableObject plus:

  • BlueprintInfo
  • IsPublishedInContext
  • LocationInfo
  • Metadata

VersionedItem

Content items such as Pages and Components. Does not include non-versioned items such as Folders or Structure Groups.

Methods and Properties available include all from RepositoryLocalObject plus:

  • LocationInfoVersionInfo

Tridion Old School Objects

Page

The page object we all know and love. Contains a mandatory property ‘FileName’ that must be set when creating new items.

Methods and Properties available include all from VersionedItem plus:

  • ComponentPresentations
  • FileName*
  • IsPageTemplateInherited
  • PageTemplate
  • WorkflowInfo

Component

The classic object containing the actual content. Contains a mandatory property of Schema.

Methods and Properties available include all from VersionedItem plus:

  • ApprovalStatus
  • BinaryContent
  • ComponentType
  • Content
  • IsBasedOnMandatorySchema
  • IsBasedOnTridionWebSchema
  • Schema*
  • WorkflowInfo

Casting the Generic IdentifiableObject to a Page object

Recently I was working on porting a classic ASP custom page to use the Core Service and was excited to learn about the copy method on this StackOverflow post. By default it returns an IdentifiableObject:

// newItem is ItemType IdentifiableObject
var newItem = client.Copy(source.Id, orgItemUri, true, new ReadOptions());

If I want to access the FileName property I need to cast it to a Page object:

// newItem is ItemType Page
var newItem = client.Copy(source.Id, orgItemUri, true, new ReadOptions()) as Page;

However, in my code I wanted to copy any kind of item and cast it to a Page if I need to. With some help from the Tridion StackOverflow community I learned how to get the ItemType and then for only Pages set the FileName property.

PageData pageData = newItem as PageData;  // Cast IdentifiableObject to Page object

Notice the new UnknownByClient (UBC) ItemType – almost a UFO!

	private string CreateNewItemCopy(string title, RepositoryLocalObjectData source, string filename)
        {
            string newItemUri = "";
            try
            {
                ItemType tridionItemType = GetTridionItemType(source);
                string orgItemUri = source.LocationInfo.OrganizationalItem.IdRef;
                var newItem = client.Copy(source.Id, orgItemUri, true, new ReadOptions());
                newItem.Title = title;
                if (tridionItemType == ItemType.Page)
                {
                    PageData pageData = newItem as PageData;
                    pageData.FileName = filename;
                    client.Update(pageData, new ReadOptions());
                }
                else
                {
                    client.Update(newItem, new ReadOptions());
                }
                newItemUri = newItem.Id;
            }
            catch (Exception ex)
            {
                throw;
            }

            return newItemUri;
        }

	private ItemType GetTridionItemType(RepositoryLocalObjectData source)
	{
		string itemType = source.GetType().Name;

		switch (itemType)
		{
			case "ComponentData":
				return ItemType.Component;
			case "PageData":
				return ItemType.Page;
		}
		return ItemType.UnknownByClient;
	}

Tridion Object Casting Is Good

Don’t worry if the method returns an IdentifiableObject and you need a concrete object such as Page or Component. Cast it to the object type you need and you’ll be cooking with gas!

Summary

It is a lot of fun learning about the Core Service. However, sometimes it is difficult getting familiar with the new ItemTypes and limited properties they provide. Becoming familiar with them and casting is essential.  What they offer allows us to use the most generic object possible for our methods yet also the opportunity to cast the object down to reality when you need to access that oh-so-special property. I hope I’ve provided a good overview of these UFOs and wish you luck in your casting adventures.

Tridion 2011 provides us with great possibilities to improve the user experience for our Authors and Editors using GUI Extensions. We can add new items to the context-menu, new icons to the ribbon, or even new tabs in the edit screen of a Component. All of this is possible thanks to the new Anguilla framework introduced in Tridion 2011. There is a lot to understand before you can make your first usable GUI Extension and in this article I will explain what I learned while creating my first GUI Extension.  This is a 3-section tutorial covering all aspects to create a GUI Extension, including the Configuration, JavaScript, and Tridion API calls.

Architecture – Tridion Custom Page (ala R4 and R5)

In the old school way using Tridion R4 or R5 we would create a custom page in classic ASP and directly instantiate the Tridion API COM+ TDS.TDSE object.  This gave us direct access to the Tridion API from our ASP Script and worked well since we had our ASP page in the Tridion CMS Website. However, we combined our display HTML with our code logic, needed to postback to the same page, and use if-conditions to determine to process the form or not. In addition, it was hard to test and debug, often limited to writing out strings of text.  Here is what the old-school Tridion Custom Page architecture looked like:

Tridion Custom Page

Architecture – Tridion R6 (aka Tridion 2011) GUI Extension

Tridion GUI Extension Overview

The main idea is the client and server are separated, with the client using mostly JavaScript and a little HTML and the server using a Web Service, .NET, C#, and the Tridion API.  The _execute method in the GUI Extension JavaScript file is run when the user clicks the GUI Extension button.  Usually we display a GUI Extension Interface popup and that popup is what does the work of getting the user input (if you need more than the item URI) and then calling a web service to do the heavy lifting with the Tridion API.  This architecture applies a clear separation of concerns – from getting and showing feedback to the user via the HTML page and interacting with the Tridion API in the Web Service layer.

Concept – Who Did It?

The goal is to show the last person who modified the item.  I call it ‘WhoDidIt’ and is part of the Sherlock namespace. I am a big fan of the books and also the recent BBC series and felt this was an appropriate name for the feature but also for the deep investigation work into how GUI Extensions are created.  It is implemented as a right-click context menu item and does not include a ribbon extension.

Overview – 3 main steps to perform when building a Tridion 2011 GUI Extension

1. Get your button in the ribbon or your link in the context menu. This is death by config. If it doesn’t kill you, there is a good chance you will have your GUI extension working.  GUI Extension button-click calls _execute method in your .js file, referenced in the config.  _execute contains a JavaScript call to show a popup

2. Show a popup HTML page when someone clicks on #1.  Popup makes an AJAX call to a web service.

3. Do something using the Tridion API from the popup via an AJAX call to the Web Service that uses the Tridion Core Service to interact with Tridion.

Some personal choices I made:

– Keep the popup plain and simple HTML and JavaScript/ jQuery. I have seen others use an ASPX page with some server-side control references. Too complicated and difficult to debug – let’s keep it simple and use JavaScript frameworks here in the client.

– Use ServiceStack for the Web Service layer – super-simple, clean, JSON-friendly, easy to call from AJAX

Sections of this tutorial:

Installing the Extension

Files: Client

Server

Installing the GUI Extension Client

1. Copy files to Tridion CMS Server. Location: Tridion\web\webUI\Editors

2. Open IIS and create a new Virtual Directory

VDIR for Tridion GUI Extension

3. Edit Tridion GUI System.Config file and tell Tridion about the new Extension. Hint: If an extension is misbehaving and breaking your GUI, comment it out in this file. Location: Program Files (x86)\Tridion\web\WebUI\WebRoot\Configuration\System.config

    <editor name="WhoDidIt">
      <installpath>C:\Program Files (x86)\Tridion\web\WebUI\Editors\WhoDidIt\</installpath>
      <configuration>WhoDidIt.config</configuration>
      <vdir>WhoDidIt</vdir><!-- Must match IIS Virtual Dir name -->
    </editor>

4. Recycle IIS Application Pool. Empty browser cache. Update from Peter K:  “You don’t have to recycle the app pool when you update the configuration. Increment the “modification” attribute in the configuration file and your cache will automatically be invalidated for all clients (so no need to clear the browser cache)”.  Test.

Installing the GUI Extension Server – Create the MVC3 Website for ServiceStack

1. Open the Visual Studio Solution and add the Tridion references to the Core Service DLL – Tridion.ContentManager.CoreService.Client dll located on the Tridion server in the bin/client folder. Also, I suggest to do a find and replace for ‘TridionDev2011’ with the CMS URL of your server. You should have about 55 replacements. (thanks to WCF config for the high # of refs)

2.  Create a new folder under Inetpub\wwwroot for the Web Service and copy the Web Service files there. If you can map a drive to your root folder on the Tridion CMS then you can easily use the ‘Publish’ option in Visual Studio to deploy your project. Simply right-click onthe project, select publish, chose file copy, then put the full path (including mapped drive) to the IIS site and select publish.

3.  Create a new App Pool in IIS for the TridionServiceStack ASP.NET 4.0 site.

4.  Create a new Website in IIS and point to the files in step #1.  I usually put this on a different port, such as 8001.

5.  Open the web.config file and change the Tridion Core Service URL references from Dev2011Tridion to your CMS Server URL.

 

Tridion GUI Extension Configuration Explained – WhoDidIt.config

Tridion Configuration is the first major step you need to understand to get a working GUI Extension. This is where we add an option to the GUI and is part of our ‘client’. We do not do any Tridion API calls in the ‘client’ part – it is all JavaScript. In my experience it was by far the most difficult part of the process and is why I dedicate 30% of this tutorial on the configuration. I extensively used the 8 steps post by Yoav Niran and the Hello World post for starting with GUI Extensions. When the going got tough I went to the Tridion StackOverflow community. Thanks to Chris Summers for helping clarify CommandSet config and John Winter for help with the GUI Extension icon.

Configuration Groups:

This is a list of resources you want available in the _execute method after the user clicks your button / link in the GUI. Not in the popup you will open very shortly from the _execute method. So, for example, if you reference jQuery here, you will have it avail in your _execute method, which is very handy, but not in your popup.

Name your config. This is one of those names you’ll use later, and when wrong, nothing will work.

<cfg:group name="Sherlock.ConfigSet" merger="Tridion.Web.UI.Core.Configuration.Resources.CommandGroupProcessor" merge="always">

3 types of files you will often see in the config:

  • <cfg:file type=”script”> – js file containing _execute method
  • <cfg:file type=”style”> – css file containing icons for extension
  • <cfg:file type=”reference”> – name of the commandset attribute, <cfg:commandset id=”Sherlock.Interface”>

* This is one of the difficult dependencies that is not documented or really explained and almost drove me mad. When this is wrong you will see a message like ‘Sherlock.Command’ does not have a reference.

OK – these are the highlights of the config section, and it is here in it’s entirety:

<cfg:groups>
  <cfg:group name="Sherlock.ConfigSet" merger="Tridion.Web.UI.Core.Configuration.Resources.CommandGroupProcessor" merge="always">
    <cfg:fileset>
          <cfg:file type="script">/client/js/dependencies/jquery.js</cfg:file>
          <cfg:file type="script">/client/js/dependencies/infoMessage.js</cfg:file>
          <cfg:file type="script">/client/js/utils/utils.js</cfg:file>
          <cfg:file type="style">/client/css/WhoDidIt.css</cfg:file>
          <cfg:file type="script">/client/js/WhoDidItCmd.js</cfg:file>
          <cfg:file type="reference">Sherlock.Interface</cfg:file>
    </cfg:fileset>
    <cfg:dependencies>
      <cfg:dependency>Tridion.Web.UI.Editors.CME2010</cfg:dependency>
      <cfg:dependency>Tridion.Web.UI.Editors.CME2010.commands</cfg:dependency>
    </cfg:dependencies>
  </cfg:group>
</cfg:groups>

Extension definition

Next is the extension definition. Basically, the text of the context-menu item and where it goes. This was the least problematic for me and almost always worked as expected. Maybe the fact it has the least amount of dependencies helped.

The text of the context-menu item is here as the ‘name’. Why didn’t they make an attribute called displayText? I have no idea, and this is one of the places where a name doesn’t have a dependency. Also, the id is not used anywhere else. The command is used later in the Commands section.

<cmenu:ContextMenuItem id="cmExtWhoDidIt" name="Who did it?" command="SherlockCommand"/>
      • Name: Text shown in content-menu
      • Command: Name of command in Commands section (discussed later)
      • ID: Used to define icon image in the CSS file

Another important dependency is to our config section above:

<ext:dependencies>
  <cfg:dependency>Sherlock.ConfigSet</cfg:dependency>
</ext:dependencies>

OK, with that behind us, we are ready to move on. Note, I did not implement a ribbon button for this extension. Full code below:

<extensions>
  <ext:dataextenders/>
  <ext:editorextensions>
    <ext:editorextension target="CME">
      <ext:editurls/>
      <ext:listdefinitions/>
      <ext:taskbars/>
      <ext:commands/>
      <ext:commandextensions/>
      <ext:contextmenus>
        <ext:add>
          <ext:extension name="SherlockExtension" assignid="" insertbefore="cm_refresh">
            <ext:menudeclaration>
              <cmenu:ContextMenuItem id="cmExtWhoDidIt" name="Who did it?" command="SherlockCommand"/>
            </ext:menudeclaration>
            <ext:dependencies>
              <cfg:dependency>Sherlock.ConfigSet</cfg:dependency>
            </ext:dependencies>
            <ext:apply>
              <ext:view name="DashboardView"/>
            </ext:apply>
          </ext:extension>
        </ext:add>
      </ext:contextmenus>
      <ext:lists/>
      <ext:tabpages/>
      <ext:toolbars/>
      <ext:ribbontoolbars/>
    </ext:editorextension>
  </ext:editorextensions>
</extensions>

Commands Section – Your link to the JS Magic

Last comes the most critical part of all – the commands section that contains a link to our js file. While not so obvious, it is the magic that makes this sparkle.

First, we link to our reference made up in the config. First dependency.

<cfg:commandset id="Sherlock.Interface">

Next, we specify the Namespace of the js file with our _execute method in it. Super important. Also, note the name of the command is used in the extensions config above.

<cfg:command name="SherlockCommand" implementation="Extensions.WhoDidIt"/>

Finally, comes the last important part of the config, the dependency on the config section.

<cfg:dependencies>
  <cfg:dependency>Sherlock.ConfigSet</cfg:dependency>
</cfg:dependencies>

Phew – we made it. Full config file here on GitHub for your viewing pleasure.

OK – that was step 1. I am almost out of breath, but let’s pretend it was only a 3 line config and we didn’t waste too much of our time with it.

Do Something – Show the users a popup from the GUI Extension

Now the fun part begins. Let’s get the JavaScript in place.

To setup the main methods we need to enable the interface, tell the Tridion GUI when our extension is available and enabled, and then give it code to fire when we hit our button. I will simply show the boiler-plate code here and you can rename the functions to suit your needs. The IsEnabled method is interesting in that if more than 1 item is selected the button is disabled in the context menu. Cool! This is something from Jaime Santos’s HellowWorldCM post. Notice the alert in the execute method.

WhoDidIt.js

Type.registerNamespace("Extensions");

Extensions.WhoDidIt = function Extensions$WhoDidIt()
{
  Type.enableInterface(this, "Sherlock.Interface");
  this.addInterface("Tridion.Cme.Command", ["WhoDidIt"]);
};

Extensions.WhoDidIt.prototype.isAvailable = function WhoDidIt$isAvailable(selection, pipeline)
{
    // Only show option for versioned items
    var items = selection.getItems();
    if (items.length > 1)
        return false;

    if (items.length == 1) {
        var item = $models.getItem(selection.getItem(0));
        if (item.getItemType() == $const.ItemType.STRUCTURE_GROUP || item.getItemType() == $const.ItemType.FOLDER || item.getItemType() == $const.ItemType.PUBLICATION) {
            return false;
        }
        return true;
    }
};

Extensions.WhoDidIt.prototype.isEnabled = function WhoDidIt$isEnabled(selection, pipeline) {
  var items = selection.getItems();
  if (items.length == 1) {
    return true;
  }
  else {
    return false;
  }
};

Extensions.WhoDidIt.prototype._execute = function WhoDidIt$_execute(selection, pipeline) {
  alert('hello');
}

OK – now we are at an interesting place and if we have our config in good order, our VDIR setup in IIS, our extension added to system.config, and all our files deployed to IIS, we might see something. Time to test. I suggest to start with my files and naming, etc, and then change 1 by 1 to your naming scheme.

JavaScript for GUI Extension

Next, let’s do something a little more interesting, show a popup. Code borrowed from Powertools / other GUI Extension examples. Note the selectedId is the item URI and part of the querystring. This is very important and we use it later on.

Extensions.WhoDidIt.prototype._execute = function WhoDidIt$_execute(selection, pipeline) {
    // Comment line below once client is working and you want to test the server
    //alert('Excellent!');

    // UnComment - Show Popup that calls Web Service using AJAX
    var selectedID = selection.getItem(0);
    var host = window.location.protocol + "//" + window.location.host;
    var url = host + '/WebUI/Editors/WhoDidIt/client/html/popup.htm?Uri=' + selectedID;
    var popup = $popup.create(url, "toolbar=no,width=400px,height=200px,resizable=false,scrollbars=false", null);
    popup.open();
};

Popup.htm:

<html>
<head>
  <script src="js/third-party/jquery-1.5.1.js" type="text/javascript"></script>
  <script src="js/third-party/jquery.ba-bbq.min.js" type="text/javascript"></script>
  <script src="js/popup.js" type="text/javascript"></script>
</head>
<body>
  <div><span id="suspect"></span> did it!</div>
</body>
</html>

Do you notice the js files were not in our extension config? That is because we do not need them within the execute method. The BBQ jQuery function is to serialize our querystring params into a json object that we pass to our web service. The AJAX magic is in the popup.js file.

popup.js contains a jQuery AJAX call to the web service:

$(function () {
    dataString = jQuery.param.querystring(window.location.href);
    $.ajax({
        type: "GET",
        url: "http://TridionDev2011:8001/Tridion2011ServiceStack/api/tridionItem",
        data: dataString,
        dataType: "jsonp",
        success: function (data) {
            $(document).ready(function () {
                $("#suspect").text(data.lastModifiedBy);
            });
        },
        error: function (request, status, error) {
            alert(request.responseText);
        }
    });
});

Use the jQuery BBQ library and serialize the Uri param from the querystring.

dataString = jQuery.param.querystring(window.location.href);

Specify the URL of our ServiceStack web service

url: "http://TridionDev2011:8001/Tridion2011ServiceStack/api/tridionItem",

Use jsonp since I run the Web Service on a different port and the browser treats it like a different domain and will not post if using only json.

dataType: "jsonp",

Update the HTML <span/> element with id=suspect after the DOM is loaded:

$(document).ready(function () {
  $("#suspect").text(data.lastModifiedBy);
});

This code runs on Page Load since we want to find out who did it right away and not waste another moment! We only need the URI to find out who did it and don’t need any extra info from the user.

OK, we’re done here on the client side of things, and while it feels like a lot of work (and is) the fun part of working with the Core Service has not yet begun.

Create the Server with Tridion API References using ServiceStack and Tridion API

I am always impressed how easy and quick it is to work with ServiceStack. I host it within an MVC 3 application, so if you decide to follow this approach you’ll need to install MVC3 on your server. Using the WebPI installer from Microsoft it is fairly painless and 100% automated.

Create ServiceStack project

The ServiceStack Web Service is created to call the Tridion API via the Core Service. You can run the Web App in Debug mode on your client development machine and step through the Core API calls. This is a very fast dev cycle when using the Tridion API and takes out the step of copying files to the server. Make sure your URL references / login info in the web.config is correct and you’re all set.

1. Create an empty MVC 3 Web App

2. Using NuGet, add a reference to ServiceStack MVC app. It downloads a lot of references.  See my ServiceStack post for more info on getting started.

3. Create a Services and Repositories folder.

4. Create a new class in the Model folder and give it string properties for whatever you want to pass back. Note, ServiceStack will camelCase all your variables for you in the JSON response.

5. Create a new class in the Services folder. Implement the RestBaseService<Type> method (see the Todo example). Have 1 method for each response type – if not using then return null.

6. Create a new class in the Repositories folder. Implement the Get method, Store, etc. I copy the ToDo repository and remove methods not used.

7. Add the URI / Model class to the App_Start/AppHost.cs file. For example,

Routes.Add<TridionItem>("/tridionItem");

8. Register the repository in the same file:

container.Register(new TridionItemRepository());

9. Update Global.asax to have /api/ ignored by MVC and avoid annoying favicon errors.

routes.IgnoreRoute("api/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");

Add Tridion code to ServiceStack GetByUri Repository method:

TridionItemRepository.cs Code to get Last Modified By (Revisor) of item:

  public class TridionItemRepository
    {
        public TridionItem GetByUri(string uri)
        {
            TridionItem tridionItem = new TridionItem();
            try
            {
                CoreServiceClient client = new CoreServiceClient();
                client.ClientCredentials.Windows.ClientCredential.UserName = ConfigurationManager.AppSettings["impersonationUser"].ToString(); // "administrator";
                client.ClientCredentials.Windows.ClientCredential.Password = ConfigurationManager.AppSettings["impersonationPassword"].ToString();
                client.ClientCredentials.Windows.ClientCredential.Domain = ConfigurationManager.AppSettings["impersonationDomain"].ToString();
                IdentifiableObjectData objectData = client.Read(uri, null) as IdentifiableObjectData;
                FullVersionInfo versionInfo = objectData.VersionInfo as FullVersionInfo;
                tridionItem.Title = objectData.Title;
                tridionItem.Uri = uri;
                tridionItem.LastModifiedBy = versionInfo.Revisor.Title;
            }
            catch (Exception ex)
            {
                tridionItem.Error = ex.Source + ", " + ex.Message + ", " + ex.ToString();
            }
            return tridionItem;
        }
    }

Steps to implement:

Core Service references. This info is also in the Tridion Live Documentation.

1. Make a reference in Visual Studio to the Tridion.ContentManager.CoreService.Client dll located on the Tridion server in the bin/client folder.

2. Make a web service reference to your server:

http://TridionDev2011/webservices/CoreService.svc

3. Update the config of the web service reference in web.config with the config in the Tridion Live documentation.

Create an instance of the core service, making sure to add user info. Thanks to Andrey’s blog post for the impersonation info.

CoreServiceClient client = new CoreServiceClient();
client.ClientCredentials.Windows.ClientCredential.UserName = ConfigurationManager.AppSettings["impersonationUser"].ToString(); // "administrator";
client.ClientCredentials.Windows.ClientCredential.Password = ConfigurationManager.AppSettings["impersonationPassword"].ToString();
client.ClientCredentials.Windows.ClientCredential.Domain = ConfigurationManager.AppSettings["impersonationDomain"].ToString();

Get a Tridion Object

 

TryCheckOut and Read are the new GetObject. Why Tridion R&D decided to take a well known method name like GetObject and replace it with Read and TryCheckOut was a big surprise to me and cost time to figure out where it went. I was lucky enough to find an example from Ryan Durkin on Uploading Images using the Core Service.

IdentifiableObjectData objectData = client.Read(uri, null) as IdentifiableObjectData;

Version Info

VersionInfo comes in 2 flavors, FullVersionInfo and BasicVersionInfo. At first I tried with BasicInfo and it did not contain the Revisor property.  It was in the FullVersionInfo  and I discovered this while debugging using the Visual Studio debugger and locals window. Also, a good time to point out that I wrote and debugged all the Core Service code on my desktop, not on the server. Very nice feature of working with the Core Service!

Model Class containing properties

namespace Tridion2011ServiceStack.Models
{
    public class TridionItem
    {
        public string Title { get; set; }
        public string Uri { get; set; }
        public string LastModifiedBy { get; set; }
        public string Error { get; set; }
    }
}

ServiceStack works with POCOs (Plan ‘Ol CLR Objects) to define DTOs (Data Transfer Objects) that contain the properties to serialize for the JavaScript client.  Populate the DTO with Tridion info and return it.  ServiceStack does the serialization behind the scenes and we don’t have to worry about it. Note, these names will be changed to camelCase and therefore look like this in the client:

        • title
        • uri
        • lastModifiedBy
tridionItem.Title = componentData.Title;
tridionItem.Uri = uri;
tridionItem.LastModifiedBy = versionInfo.Revisor.Title;

Test

The Web Service can be tested using the HTML page in the Visual Studio Solution named GetTridionItem.htm.  Use the FireBug debugger and the network tab to see the response sent back from the Web Service.  Make sure to change the URL of the web service to the yours.  The URL should be the local URL from your debugging session – something like this http://localhost:61860/api/tridionItem .

Testing the GUI Extension client is best done with a js alert.  Once this is working then you should test calling the Web Service.  Again, make sure the URL to the web service is correct.  Use firebug to test this.  If you do more extensive coding in your _execute method then see my tips for debugging the GUI Extension js.

Check the web.config and make sure your Tridion User Info is there as well as the URL pointing to your CMS.

If you are running locally to test your Web Service Tridion API calls, change the URL in the TridionGetItem.htm page to something like:

url: "http://localhost:61860/api/tridionItem"

Set the breakpoint in the Repository class or even the js of your htm page and have fun debugging!

Summary

It is a real good feeling becoming familiar with the GUI extension framework and having so much potential at our fingertips. Once everything is setup and configured it is much easier to add new features and iterate on a working example. I hope I have helped demystify the elements in a GUI extension and given you a good place to start working with it. This article could not have been possible without all the GUI Extension and Core Service examples already created in the community as well as the official Tridion documentation. I hope we can continue improving the architecture of the config file and setup to make it even easier for developers to get started with writing extensions. Please give feedback. Thanks!

Download

Get the source code on GitHub.