HomeDev GuideRecipesAPI Reference
Dev GuideAPI ReferenceUser GuideLegal TermsGitHubNuGetDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Drag-and-drop

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.

📘

Note

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 a drag target.

define("epi/samples/dnd/DndSamples",
  [
    "dojo/_base/declare",
    "dojo/dom-construct",
    "dojo/when",
    "dijit/_Widget",
    "dijit/_TemplatedMixin",

    // Optimizely dnd classes
    "epi/shell/dnd/Source",
    "epi/shell/dnd/Target"
  ],

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

      postCreate: function () {
        this.inherited(arguments);
        this._setupSource();
        this._setupTarget();
      },

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

        source.insertNodes(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.
            console.log(value);
          }.bind(this));
        }
      }
    });
  }
);

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 described in the following list:

  • epi.cms.pagereference
    epi.cms.blockreference
    epi.cms.folderreference
    epi.cms.contentreference – 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.
  • fileurl – A URL to any file, such as /myvpp/myfolder/mydocument.pdf.
  • imageurl – A URL to an image, such as /myvpp/myfolder/myimage.jpg.
  • link – An 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. This 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 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 all 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.

Example:

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. This content can be dragged 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 the 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:

define([
  ...
  "epi-cms/conversion/ContentLightUriConverter",
  "epi/shell/conversion/ObjectConverterRegistry"
], function (
  ...
  ContentLightUriConverter,
  ObjectConverterRegistry
) {
  ...
  var converter = new ContentLightUriConverter();
  ObjectConverterRegistry.registerConverter("link", "epi.cms.content.light", converter);
});

The following example shows how to convert between content dnd formats. In a standard installation, this is used to 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.

define([
    "dojo/_base/declare",
    "dojo/when",
    "epi/dependency",
    "epi/UriParser",
    "epi/cms/core/ContentReference"
  ],
  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
            };
          });
        }
      }
    });
  }
);