The Tridion MVP retreat is hosted by SDL to honor those who share the most with the community throughout the year. I was lucky enough to be part of this group for my efforts in 2012.

One thing not known by many first-timers like myself is that the event is full of surprises. In this short post I will reveal some of the surprises that were in store for us during the 3 days.

Surprise 1:  Take your laptops with you to breakfast.  What?!

Turns out that after our 1 hour bus ride and for some people a 15 hour flight we would not have the luxury to relax, check in to our rooms, and get comfortable.  After a delicious breakfast buffet we headed straight to our working quarters – a large meeting room with a glass wall overlooking the beautiful valley and swimming pool.  The wireless, projector, and places were all arranged and waiting for us.  Nuno started off with an inspirational talk about how successful we were at sharing last year (hundreds of blog posts between the 20 of us) and also shared some secrets about the next version of Tridion and where the product is heading.

After his talk we had 1 hour to form teams, think of what idea we will finish programming within 2 days and launch it to the community as open source.  I joined the GUI Extension team and we decided to extend the Schema Edit screen and created the Field Behavior Injector to allow other field attributes, based on some previous code by Jaime Santos.  Bart Koopman has a nice article here about it.

Surprise 2:  Quickest way to the castle is by horse

Several of us gathered around the lobby wondering what surprise waited for us next.  We weren’t asked to bring laptops, so no surprise work awaited.  Hearing jingling bells in the distance we were all surprised when some horses and carriages appeared and this was our transportation to dinner – in the ancient castle at the top of the hill!

Surprise 3:  The local drink of Obidos is made from Cherries

Before dinner we were lucky to enjoy some drinks in the lobby and sampled some of the local digestive made from cherries – it was delicious, but a bit too sweet for a pre-dinner drink.  The bartender was very good and offered us local port and also his own Martini recipe.  Saturday we would take a trip to the local factory that produces this delicious drink – and who would have guessed that the quickest way is by off-road 4×4 vehicles driving through the Portugese countryside?

Surprise 4:  MVPs are a musical troupe

In the evenings we were surprised to learn that Quirijn Slings is a Ukelele master and has a slew of songs in his repertoire and Dominic Cronin plays a mean Harmonica.  Once the music started playing various MVPs showed off their musical talents and we had many sing-a-longs to share more with the local community.

Surprise 5:  It’s all about sharing

As soon as we started programming we also started discussing how to share what we were making.  Bart Koopman blogged about using Resource strings in GUI Extensions and John Winter wrote the first post about what our teams were building – while we were still building it.  Alvin Reyes had our Google Code site up and running with an introduction and overview immediately – preparing the location for our code the next day.  And I dug into building a GUI Extensions installer with Powershell that creates the required files, folders, IIS Settings, and config file updates.

Surprise 6:  We start everyday at 9:00 sharp in the meeting room

Despite the food and beverages consumed the previous night we were all expected to be present at the meeting room at 9:00 to begin the next days’ hacking session.  Surprisingly, it took little effort or time for us all to get sunk into hacking code and soon a strange intensity was building up in the room, and before we knew it we were making great progress on our new projects.

Surprise 7:  Your drinks are being served by the local Mayor

Our last dinner was held at an incredible local eco restaurant where they have created everything from local and sustainable materials.  It is owned by the local Mayor and he was on hand to serve drinks and invite us to taste his local specialties!  They were very delicious and he was a great host.

Surprise 8:  It’s over!

After 3 intense days we were already saying goodbyes and pushing each other to share more so we will see everyone again next year.  But, I have a feeling some of us won’t be there next year and also that some new faces might join the musical troupe to create some new songs to carry us through the night.

 

 

The Tridion GUI is powered by JavaScript while the backend is powered by .Net and sending a (nice) message from .Net to the GUI has been very challenging – until now. Using SignalR, an open-source product from Microsoft, it is possible to show or ‘echo’ the message from the .Net Event System, Templates or Customer Resolvers and show them in the GUI. Check out the tutorial that Will Price and I wrote published on SDL Tridion World to find out how to implement this yourself. Special thanks to SDL’s Bart Koopman for helping get the article online and embracing the GitHub Gist embed for source code.

Tridion 2013 works with both Visio 2010 and 2013, and removes support for older versions of Visio.  Today I tried to create a new Workflow using Visio 2013 and I would like to share an important tip that might save you some time.

The Tridion Visio Stencils are located in the ‘ADD-INS’ menu item.

However, attempting to create a new Workflow will be unsuccessful in your default Visio / Tridion workflow install.  You will see the following error message:

Visio blocks these ADD-INS for our own security (was there ever a problem with rampant Visio plugins??)

Solution:

1.  Go to File, Options
2.  Trust Center Settings


3.  File Block Settings 

4. UnCheck all checkboxes.  Yes, Uncheck them.   I know it seems unintuitive, but we are in the ‘Block’ settings and we do not want Visio to block us from opening or saving these item types.

5.  Select OK and then go back to your new Visio Workflow diagram.  It should allow you to create a new Workflow diagram now.

The icons all look almost the same, and the tooltips don’t give us any hints.  For me this was a struggle to figure out which save button to press.  Hopefully this will be fixed in a future SP.

Tridion C# TBB Upload Tip

June 10th, 2013 | Posted by Robert Curlette in .NET | Tridion - (0 Comments)

When saving a C# TBB I recently had the error message ‘Error 1 Invalid URI: The format of the URI could not be determined.’?

My first thought was to check the URI in the AssemblyInfo.cs file – was it the correct folder URI?  Did I have a typo?  Did the folder exist?  But, all was fine.

Then I recalled seeing this error before and it is related to the config.xml file that we create using TcmUploadAssembly.exe to upload the TBB.  In this file we must specify the URL of the server, which is, of course, also a URI.

The wrong value was:  <targetURL>localhost</targetURL>

Correct:  <targetURL>http://localhost</targetURL>

So, just in case you ever hit this error I hope this little tip can save you some time.

Sometimes, especially for System Administration or Developer Extensions, we don’t want to have the Context Menu GUI Extension visible to the end user.

We can use the IsAvailable option in the GUI Extension Command.js file and set it to false if the user is in a not allowed group. The following code can be used for hiding a GUI Extension context menu item.

Extensions.SetPermissions.prototype.isAvailable = function SetPermissions$isAvailable(selection, pipeline) {
    var showOption = true;
	var groupsWithNoAccess = ["Author","Chief Editor"];

	// Get the groups the user belongs to
	var groups = Tridion.UI.UserSettings.getJsonUserSettings(true).User.Data.GroupMemberships;
	if(groups["@title"] == undefined)  // 1 group membership
	{
		if(groups.Group["@title"] == groupsWithNoAccess[0])
			showOption = false;
	}
	else
	{
		// if the user belongs to a group with no access, hide the option
		for (var i = 0; i < groups.length; i++) {
		    var userIsInGroup = groups[i]["@title"];
		    length = groupsWithNoAccess.length;
		    while(length--) {
		         if (userIsInGroup.indexOf(groupsWithNoAccess[length])!=-1) {
			      showOption = false;
		              return showOption;
			 }
		     }
		}
	}
	return showOption;
};

 

Show Context Menu Item for Authorized Groups
And, if we want th do the inverse, and show the Context Menu item for only authorized groups we would use the following:


Extensions.SetPermissions.prototype.isAvailable = function SetPermissions$isAvailable(selection, pipeline) {
        var showOption = false;
	
	var groupsWithAccess = ["Administrator"];  // Specify all access groups here
	
	var groups = Tridion.UI.UserSettings.getJsonUserSettings(true).User.Data.GroupMemberships;
	if(groups["@title"] == undefined)  // 1 group membership
	{
		if(groups.Group["@title"] == groupsWithAccess[0])
			showOption = true;
	}
	else
	{
		// many group memberships
		for (var i = 0; i < groups.length; i++) {
			var userIsInGroup = groups[i]["@title"];		
			length = groupsWithAccess.length;
			while(length--) {
			   if (userIsInGroup.indexOf(groupsWithAccess[length])!=-1) {
				   showOption = true;
				   return showOption;
			   }
			}
		}
	}
	
	return showOption;
};

In my previous article I described how to use a Console application to check-in items using the Core Service. In this article I will describe how to setup and use the log4net framework to output content to the Console window and also to a log file.

1. Get the log4net DLLs using NuGet. Right-click on the project name and select ‘Manage Nuget Packages’. If you do not have this option then download NuGet and then try again.

2. Update the app.config file with the log4net configuration. NuGet packages can modify our config files, but unfortunately the log4net NuGet package does not.

This configuration does the following:
– Specifies a Console Logger to automatically write all messages to the console
– Uses a Date rolling file appender

<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="Console" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level: %message%newline" />
</layout>
</appender>
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="File" value="CheckinItems.log"/>
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<maxSizeRollBackups value="7" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%-5level %date{yyyy-MM-dd HH:mm:ss} - %m%n"/>
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="LogFileAppender" />
<appender-ref ref="Console" />
</root>
</log4net>
</configuration>

3. Add this code to your Main method to startup log4net

XmlConfigurator.Configure();

4. Instantiate an instance of a logger

private readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

5. Write a log message

log.Info("log something");

What happens if you have hundreds, or thousands of items you want to check-in? One solution is to check-in the items using the GUI, My Tasks, All Checked out items option. However, there is another way and that is to run a script to check-in the items automatically. This is also helpful if you want to check-in all items before running another script.

Running scripts in Tridion is a common activity on large implementations. There’s always some data manipulation we want to do in bulk and save the Editors and Authors hours of time. Sometimes, however, during these bulk operations we have a failure, and sometimes items remain checked out.

In this article I will describe how to create a Core Service Console Application to check-in Tridion items. The following code uses the Tridion Core Service and can run from any Tridion instance.

Step 1: Creating the Core Service App and References

1. Create a new Console Application.

2. Reference the Tridion Core Service DLL

3. Create an App.config file

Copy the Core Service config from C:\Program Files (x86)\Tridion\bin\client\Tridion.ContentManager.CoreService.Client.dll.config

4. Reference 2 Microsoft DLLs:

  • System.Runtime.Serialization
  • System.ServiceModel

5. Add the using statement for the Tridion Core Service.

using Tridion.ContentManager.CoreService.Client;

Summary:
Creating the core Service app is relatively simple and straight-forward. The good news is we now have everything we need to get started writing some code.

Step 2: Using the Core Service to find Checked-out items

1. Set the binding. The Tridion Core Service comes with 2 binding options. It depends on your client (.Net, Java, etc) and also the ports avaialble (netTCP requires port 2660). For this example I will ue NetTCP since it is the fastest binding and most often used.

The binding is in the App.config file.

<endpoint name="netTcp_2011"

// use the endpoint name in our C# code
class Program
    {
        private static string binding = "netTcp_2011";
		//....
	}

2. Create an instance of the Core Service client and get all checked out items. * The magic here is in which filter to use. Big thanks to Andrey (aka Mr. P) for his help. how-do-i-get-a-list-of-checked-out-items-with-the-core-service

I always wrap the code in a using statement to displose of resources. The SystemWideListFilter is a special one that has magic powers to get items not conatined in any Blueprint Publication.

public static XElement FindCheckedOutItems()
{
	using (SessionAwareCoreServiceClient client = new SessionAwareCoreServiceClient(binding))
	{
		RepositoryLocalObjectsFilterData filter = new RepositoryLocalObjectsFilterData();
		XElement checkedOutItemsXml = client.GetSystemWideListXml(filter);
		return checkedOutItemsXml;
	}           
}

Step 3: Using the Core Service to Check-in items

1. Check-in the items with the Core Service

private static void CheckinItems(XElement items)
{
	using (SessionAwareCoreServiceClient client = new SessionAwareCoreServiceClient(binding))
	{
		foreach (XElement tridionItem in items.Nodes())
		{
			if (tridionItem.Attribute("Type").Value == "16" || tridionItem.Attribute("Type").Value == "64")
			{
				client.CheckIn(tridionItem.Attribute("ID").Value, new ReadOptions());
			}
		}
	}
}

That’s it. Here’s the final solution code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tridion.ContentManager.CoreService.Client;
using System.Xml.Linq;

namespace CheckinItems
{
    class Program
    {
        private static string binding = "netTcp_2011";
        static void Main(string[] args)
        {
            CheckinItems(FindCheckedOutItems());
        }

        public static XElement FindCheckedOutItems()
        {
            using (SessionAwareCoreServiceClient client = new SessionAwareCoreServiceClient(binding))
            {
                RepositoryLocalObjectsFilterData filter = new RepositoryLocalObjectsFilterData();
                XElement checkedOutItemsXml = client.GetSystemWideListXml(filter);
                return checkedOutItemsXml;
            }           
        }

        private static void CheckinItems(XElement items)
        {
            using (SessionAwareCoreServiceClient client = new SessionAwareCoreServiceClient(binding))
            {
                foreach (XElement tridionItem in items.Nodes())
                {
                    if (tridionItem.Attribute("Type").Value == "16" || tridionItem.Attribute("Type").Value == "64")
                    {
                        client.CheckIn(tridionItem.Attribute("ID").Value, new ReadOptions());
                    }
                }
            }
        }
    }
}

Summary

The Core Service is a powerful tool in our Tridion toolbelt. With the right filters we can work magic, retrieving lists of items never thought possible. A big thanks for the help from the Tridion community for sharing information!

MVP Award, 2013 and Learning

January 22nd, 2013 | Posted by Robert Curlette in Lifehacking - (1 Comments)

The best part of sharing is learning. When we share, we invite others to participate in the solution. We need to learn to share, and by sharing we learn from others, with their valuable comments and feedback. It is this feedback loop that we benefit from as a community.

The SDL Tridion MVP award was created to recognize those who share their experiences and knowledge through public blogs, StackOverflow, and other media. I feel honored to be awarded the MVP award for sharing in 2012.

But, this is a new year and the clock resets on the MVP award. We are all now equal and although I shared a lot in 2012 I need to continue sharing in 2013 if I want to have a chance of winning the MVP award again.

With 2013 I welcome all the new bloggers that will contribute more to our community, challenge our ways of thinking, and introduce new ways of solving old problems. The Tridion 2013 product is around the corner. Ask your manager to install it on the Dev machine. Write about the installation, or one of the many new features.

The secret of writing is finding something exciting to write about. If you are excited and curious about a topic, it is much easier to write and also the quality is much better. So, find something interesting, technically, to write about! It could be Tridion, .Net, Java or Web Services. Processes, management or functional designs. But, write a small article and see where it goes! And, if you make mistakes, you can always edit the article and fix them. I know I have!

Speaking of learning, Pluralsight has a great course on ServiceStack (web service framework) and with this link you can get a 1 day free trial. http://pluralsight.com/training/TwitterOffer/seriesa

I wish you all a successful year of learning!

I wanted to create a simple app to store data using the elegant OrmList from ServiceStack.  During this process I found a couple of things that cost me some time and I wanted to share these with you.  In this article I will walk through creating a small and simple ServiceStack OrmLite example.  It should take around 5 minutes to complete the example.

Please see the excellent OrmLite official docs for more examples.

Update Jan 6, 2013:  Code samples updated with the great feedback in the comments from Demis Bellot.

Step 1:  Create a Console App and Reference OrmLite.SQLite

– Create a Console app named OrmLiteExample

– Add the OrmLite references from NuGet (http://www.nuget.org).  Right-click on the References in the Solution Explorer, Manage NuGet Packages, Online, OrmLite.  Or, from the Package Manager Console (View, Other Windows, Package Manager Console) type ‘Install-Package ServiceStack.OrmLite.Sqlite32’.

Step 2:  Create the DTO (aka Model)

This is the nicest part of working with ServiceStack.  Everything is DTO and Model driven – no config files to create and the code is simple and concise.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ServiceStack.DataAnnotations;

namespace OrmLiteExample
{
    class Note
    {
        [AutoIncrement] // Creates Auto primary key
        public int Id { get; set; }

        public string SchemaUri { get; set; }
        public string NoteText { get; set; }
        public DateTime? LastUpdated { get; set; }
        public string UpdatedBy { get; set; }
    }
}

Step 3:  In program.cs, add the Using statements

using ServiceStack.OrmLite;
using ServiceStack.OrmLite.Sqlite;

The using statement ServiceStack.OrmLite.Sqlite will throw an exception if you build now.

Fix:  With NuGet, download and install ServiceStack.OrmLite.Sqlite32 package. Also, go to the properties of your project and make sure the target framework is set to .NET framework 4.  Solved!

Step 4:  Add the connection strings for SqLite

Thanks to the nice Unit Tests in the ServiceStack project I was able to use their example.

public static string SqliteMemoryDb = ":memory:";
// Updated per comment form Jonas.  First connection string below causes an app pool restart!  Use MapHostAbsolutePath instead!
//public static string SqliteFileDb = "~/App_Data/db.sqlite".MapAbsolutePath();  
public static string SqliteFileDb = "~/App_Data/db.sqlite".MapHostAbsolutePath();
Problem:  The MapAbsolutePath is not found.
Fix: Include the missing library:

using ServiceStack.Common.Utils;

Step 5:  Create the connection

//Using Sqlite DB- improved
var dbFactory = new OrmLiteConnectionFactory(
                SqliteFileDb, false, SqliteDialect.Provider);

// Wrap all code in using statement to not forget about using db.Close()
using (var db = dbFactory.Open()) {

Step 6: Create the Table

 

db.CreateTableIfNotExists<Note>();

Step 7:  Insert a record

Note:  This is different than the excellent online examples.  I received a warning message to use the Connection instead of the Command object.

// Insert
db.Insert(
	new Note {
		SchemaUri = "tcm:0-0-0",
		NoteText = "Hello world 5",
		LastUpdated = new DateTime(2013, 1, 5),
		UpdatedBy = "RC" });

Step 8:  Read the data

 // Read
var notes = db.Where<Note>(new { SchemaUri = "tcm:0-0-0" });
foreach (Note note in notes)
{
	Console.WriteLine("note id=" + note.Id + "noteText=" + note.NoteText);
}
Console.ReadLine();

Full Code

Program.cs

Note.cs

This is a small example of how to create a context-menu extension to copy the URI of a Tridion Item.  

Getting Started

The most important and also difficult part of every GUI Extension is the configuration file.  It’s best to start with a previous working example.  For this article I used the classic 8 Steps post from Yoav Nirlan.

Create the Directory

Create a new Folder in:
C:\Program Files (x86)\Tridion\web\WebUI\Editors\CopyUri

Configuration File

I started with the configuration file from the Tutorial and also loaded the Schemas into Visual Studio from Tridion.  This was a very important step because Tridion 2011 SP1 has a slightly different XML Schema for the Context Menu than Yoav’s sample does.  I relied on the intellisenne in Visual Studio and the XML Schema to get a properly formatted Configuration file.  In the future I will start with this updated config file below for Context-menu extensions.

Save it as CopyUri.config in the folder created above.

<?xml version="1.0"?>
<Configuration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge" xmlns:cfg="http://www.sdltridion.com/2009/GUI/Configuration" xmlns:ext="http://www.sdltridion.com/2009/GUI/extensions" xmlns:cmenu="http://www.sdltridion.com/2009/GUI/extensions/ContextMenu">
  <resources cache="true">
    <cfg:filters/>
    <cfg:groups>
      <cfg:group name="Extensions.Resources.CopyUri" merger="Tridion.Web.UI.Core.Configuration.Resources.CommandGroupProcessor" merge="always">
        <cfg:fileset>
          <cfg:file type="script">/Commands/CopyUriCommand.js</cfg:file>
          <cfg:file type="reference">2011Extensions.Commands.CopyUri</cfg:file>
        </cfg:fileset>
        <cfg:dependencies>
          <cfg:dependency>Tridion.Web.UI.Editors.CME</cfg:dependency>
          <cfg:dependency>Tridion.Web.UI.Editors.CME.commands</cfg:dependency>
        </cfg:dependencies>
      </cfg:group>
    </cfg:groups>
  </resources>
  <definitionfiles/>
  <extensions>
    <ext:editorextensions>
      <ext:editorextension target="CME">
        <ext:editurls />
        <ext:listdefinitions/>
        <ext:taskbars/>
        <ext:commands />
        <ext:commandextensions/>
        <ext:contextmenus>
          <ext:add>
            <ext:extension name="CopyUriExtension" assignid="ext_copyuri" insertbefore="cm_refresh">
              <ext:menudeclaration externaldefinition="">
                <cmenu:ContextMenuItem id="ext_CopyUri" name="Copy URI" command="CopyUri"/>
              </ext:menudeclaration>
              <ext:dependencies>
                <cfg:dependency>Extensions.Resources.CopyUri</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>
    <ext:dataextenders/>
  </extensions>
  <commands>
    <cfg:commandset id="2011Extensions.Commands.CopyUri">
    <cfg:command name="CopyUri" implementation="Extensions.CopyUri"/>
    <cfg:dependencies>
    <cfg:dependency>Extensions.Resources.Base</cfg:dependency>
    </cfg:dependencies>
    </cfg:commandset>
  </commands>
  <contextmenus/>
  <localization/>
  <settings>
    <defaultpage>/Views/Default.aspx</defaultpage>
    <navigatorurl>/Views/Default.aspx</navigatorurl>
    <editurls/>
    <listdefinitions/>
    <itemicons/>
    <theme>
      <path/>
    </theme>
    <customconfiguration/>
  </settings>
</Configuration>

Important parts of the Configuration File

Location of the command in the Context Menu:

insertbefore="cm_refresh"

Text of the menu item:

name="Copy URI"

Important info for the JavaScript in the next part:
1.  Command name
2.  Implementation

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

The command name is also used here:

<cmenu:ContextMenuItem id="ext_CopyUri" name="Copy URI" command="CopyUri"/>

The Commands are in the xml root ‘commands’ node, not the ‘ext:commands’.

 </extensions>
  <commands>
    <cfg:commandset id="2011Extensions.Commands.CopyUri">
    <cfg:command name="CopyUri" implementation="Extensions.CopyUri"/>
    <cfg:dependencies>
    <cfg:dependency>Extensions.Resources.Base</cfg:dependency>
    </cfg:dependencies>
    </cfg:commandset>
  </commands>
  <contextmenus/>

No Ribbon button was added in this demo.

JavaScript Code

1.  Create a new folder ‘Commands’.
2.  Create a new file called CopyUriCommand.js

Type.registerNamespace("Extensions");

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

Extensions.CopyUri.prototype.isAvailable = function CopyUri$isAvailable(selection) {
    return true;
}

Extensions.CopyUri.prototype.isEnabled = function CopyUri$isEnabled(selection) {
    if (selection.getItems().length > 1)
        return false;
    else
        return true;
}

Extensions.CopyUri.prototype._execute = function CopyUri$_execute(selection) {
    selectedItem = selection.getItems()[0];
    prompt("Copy the Item ID using Ctrl/Cmd + C:", selection.getItems()[0]);
}

 Code Explained:

1.  The isEnabled method tells the GUI if the menu option is enabled or disabled.  This extension shows the URI for only one item, so I only enable the extension if one item is selected.
2.  The execute method does the action.  We have available all methods and objects in the Tridion Anguilla framework.  The getItem method returns null but we are lucky that getItems returns whatever is selected in the GUI.  We use the prompt method of JavaScript to provide a nice small window to copy the URI from.

Creating the Virtual Directory in IIS

Create a new Virtual Directory in WebUI/Editors called CopyUri.  This name needs to be the same as the VDIR in the config from the next step.  You may need to update the Security settings for the folder and allow the Network Service user read access.  Use the ‘Test’ button after setting up and you should have a checkmark next to the first test.  The second test will fail and this is normal.

Enabling the Extension

Open the System.config file located at:  <Tridion_home>\web\WebUI\WebRoot\Configuration

Add the following to ‘turn on’ the Extension.  If your Tridion GUI stops working – comment out the following line and it should return to normal.  Double-check your config and settings.

<editor name="CopyUri" xmlns="http://www.sdltridion.com/2009/GUI/Configuration">
	<installpath xmlns="http://www.sdltridion.com/2009/GUI/Configuration">C:\Program Files (x86)\Tridion\web\WebUI\Editors\CopyUri\</installpath>
		  <configuration xmlns="http://www.sdltridion.com/2009/GUI/Configuration">CopyUri.config</configuration>
		  <vdir xmlns="http://www.sdltridion.com/2009/GUI/Configuration">CopyUri</vdir>
</editor>

Copying the WebDav URL

Big thanks to Alex Klock from Tridion Community for answering my question about getting the WebDavURL in Anguilla.  Here is the code we could add to the execute method above to show the WebDav URL instead of URI.

var item = $models.getItem(selectedItem),
    webDavUrl = item.getWebDavUrl();

if (!webDavUrl) {
    // WebDavUrl for cached item hasn't been loaded yet, so lets load it.
    $evt.addEventHandler(item, "loadwebdavurl", function (event) {
        webDavUrl = item.getWebDavUrl(); // also could do event.source.getWebDavUrl()
    });
    item.loadWebDavUrl();
}

Summary

This is a simple GUI Extension but it highlights some basics we need to know when developing a GUI Extension.  The Developer Console in the Chrome Browser was a big help while debugging and writing the JavaScript code.  A big thanks to Hristo Chakarov for his help with the JavaScript development in this post.