HomeDev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunitySubmit a ticketLog In
GitHubNuGetDev CommunitySubmit a ticket


Describes how drag-and-drop works in the Optimizely Content Management System (CMS) user interface.

Drag-and-drop (dnd) in Optimizely Content Management System (CMS) uses the "dnd" system in Dojo with some additions. See the Dojo Drag'n'Drop Redux tutorial.



To use the additional functionality in CMS your drag source or target class should implement the CMS shell classes instead of the Dojo base classes (epi/shell/dnd/Source or epi/shell/dnd/Target).

Create sources and targets

The following example is a fully functioning component that implements a drag source and target.


    // Optimizely dnd classes

  function (declare, domConstruct, when, _Widget, _TemplatedMixin, Source, Target) {
    return declare([_Widget, _TemplatedMixin], {
      templateString: '\
                  <ul dojoAttachPoint="source"></ul>\
                  <ul dojoAttachPoint="target"><li>Test</li></ul>\
      //Seems to be a bug when we have an empty list so we just create a dummy list item for the example.

      postCreate: function () {

      _setupSource: function () {
        var source = new Source(this.source, {
          creator: this._createDragItem.bind(this),
          copyOnly: true,
          selfAccept: false,
          selfCopy: false

              url: "http://www.fakecompany.com",
              name: "Fake Company",
              text: "Company web site",
              type: ["link"]
              url: "http://www.episerver.com",
              name: "EPi",
              text: "EPiServer",
              type: ["link"]
              url: "http://www.dn.se",
              name: "DN",
              text: "Daily news",
              type: ["link"]

      _setupTarget: function () {
        var target = new Target(this.target, {
          accept: ["link", "someothertype"],
          //Set createItemOnDrop if you're only interested to receive the data, and not create a new node.
          createItemOnDrop: true
        this.connect(target, "onDropData", "_onDropData");

      _createDragItem: function (item, hint) {
        var node;

        if (hint == "avatar") {
          //This node is used when dragging the item.
          node = domConstruct.create("div", {
            innerHTML: item.text
        } else {
          node = domConstruct.create("li", {
            innerHTML: item.text
        return {
          "node": node,
          "type": item.type,
          "data": item

      _onDropData: function (dndData, source, nodes, copy) {
        //Drop item is an array with dragged items. This example just handles the first item.
        var dropItem = dndData ? (dndData.length ? dndData[0] : dndData) : null;

        if (dropItem) {
          //The data property might be a deffered so we need to call dojo/when just in case.
          when(dropItem.data, function (value) {
            //Do something with the data, here we just log it to the console.

Data types

Because JavaScript is a loosely typed language, you should treat data types as a contract or interface. CMS uses several known types of drag and drop, which are in the following table:

Type nameData format
A string representation of a content reference, such as 3_122. Some converters convert specific types (page references) to content references. A drag target that accepts content references also accepts page references, not vice versa.
fileurlA URL to any file, such as /myvpp/myfolder/mydocument.pdf.
imageurlA URL to an image, such as /myvpp/myfolder/myimage.jpg.
linkAn object with two properties:

- url – the URL to the resource
- text – the inner text for the link
"epi.cms.content.lighturi (there also are specific versions like epi.cms.page.lighturi)An object with two properties:

- name – The name of the page
- uri – The string representation of the content URI, which might or might not include version information such as epi.cms.contentdata:///8.
epi.cms.content.light (there also are specific versions like epi.cms.page.light)An object with two properties:

- name – The name of the page
- contentLink – The string representation of the content reference without any version information, such as 123.

Type converters

You might want to support drag and drop to or from something you do not control the source code. You also might want to convert the dragged data to another format. Because you might specify types as an array, for the source and the target, specify types that the source delivers and the target accepts.

  • For a target, you might have to convert the data on acceptance.
  • For the source, this works fine as long as you can create the object and still support several types.


A dnd source delivers image URLs. It may specify the type as ["imageurl", "fileurl"] because these types expect the object to be a string with the URL to the actual content. You can drag this content to targets that accept an "imageurl" or a "fileurl". However, if you want to drag this content to a target that accepts a link, the source cannot create an object that can be an "imageurl" or "fileurl" and a link at the same time because the link expects a complex object with two properties: url and text.

CMS added object type converters to handle these scenarios. These are simple classes that implement a convert method with sourceDataType, targetDataType and data as parameters. You can use a singleton converter registry to register a converter in the initialization method of your module class, for combinations of conversions:

], function (
) {
  var converter = new ContentLightUriConverter();
  ObjectConverterRegistry.registerConverter("link", "epi.cms.content.light", converter);

The following example shows how to convert between content dnd formats. A standard installation lets you drag pages from the page tree to a page selector to select a page reference or a TinyMCE editor to create a link to the page.

], function (declare, when, dependency, UriParser, ContentReference) {

  return declare([], {
    // summary:
    //    Converts data between light weight content types containing an URI as a specifyer and other types.
    // tags:
    //    public

    _pageDataStore: null,

    constructor: function (params) {
      // summary:
      //    The constructor function
      // params: Object
      //    The parameters that define the object.
      // tags:
      //    public

      this._pageDataStore = this._pageDataStore || dependency.resolve("epi.storeregistry").get("epi.cms.contentdata");

    registerConverter: function ( /*Object*/ registry) {
      // summary:
      //    Registers the type conversions that this class supports to the given registry.
      // registry: Object
      //    The converter registry to add mappings to.
      registry.registerConverter("epi.cms.page.lighturi", "link", this);
      registry.registerConverter("epi.cms.page.lighturi", "epi.cms.pagereference", this);
      registry.registerConverter("epi.cms.block.lighturi", "epi.cms.blockreference", this);
      registry.registerConverter("epi.cms.folder.lighturi", "epi.cms.folderreference", this);
      registry.registerConverter("epi.cms.content.lighturi", "epi.cms.contentreference", this);
      registry.registerConverter("epi.cms.content.lighturi", "epi.cms.content.light", this);
      registry.registerConverter("epi.cms.page.lighturi", "epi.cms.content.light", this);
      registry.registerConverter("epi.cms.block.lighturi", "epi.cms.content.light", this);
      registry.registerConverter("epi.cms.folder.lighturi", "epi.cms.content.light", this);

    convert: function ( /*String*/ sourceDataType, /*String*/ targetDataType, /*Object*/ data) {
      // summary:
      //    Converts data between two types.
      // sourceDataType: String
      //    The source data type.
      // targetDataType: String
      //    The target data type.
      // converter: Object
      //    The class that performs the actual conversion.
      // tags:
      //    public
      if (!data) {
        return null;
      } else if (targetDataType === "epi.cms.content.lighturi") {
        return data;
      var uri = new UriParser(data.uri);
      var link = uri.getId();
      var versionAgnosticId = new ContentReference(link).createVersionUnspecificReference().toString();

      if (targetDataType === "epi.cms.content.light" ||
        targetDataType === "epi.cms.page.light" ||
        targetDataType === "epi.cms.block.light" ||
        targetDataType === "epi.cms.folder.light") {
        return {
          contentLink: versionAgnosticId,
          name: data.name
      } else if (targetDataType === "epi.cms.contentreference" ||
        targetDataType === "epi.cms.pagereference" ||
        targetDataType === "epi.cms.blockreference" ||
        targetDataType === "epi.cms.folderreference") {
        return versionAgnosticId;
      if (targetDataType === "link") {
        //should only be available for pages right now but allow anything for future types as files etc.
        return when(this._pageDataStore.get(versionAgnosticId), function (page) {
          return {
            url: page.properties.pageLinkURL,
            text: page.name