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:
Architecture – Tridion R6 (aka Tridion 2011) GUI Extension
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 Tridion GUI Extension
- Tridion GUI Extension Configuration Explained
- JavaScript behind GUI Extension
- HTML Popup from the GUI Extension
- Call the Tridion API using ServiceStack Web Service
- Add Tridion Core Service code to Web Service Get method
- Download
Installing the Extension
Files: Client
- System.config
- WhoDidIt.config
- /client/js/WhoDidItCmd.js
- /client/html/popup.htm
- /client/html/js/popup.js
- /client/dependencies/jquery.js
- /client/dependencies/infoMessage.js
- /client/utils/utils.js
Server
- IIS Site
- .NET ServiceStack MVC3 Web AppTridion API Calls
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
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.
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(); };
<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.
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!