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) extends the Dojo DND system with CMS-specific functionality. Use these extensions to add custom drag sources, drop targets, and type converters to the editing interface.
NoteImplement the CMS shell classes (
epi/shell/dnd/Sourceorepi/shell/dnd/Target) instead of the Dojo base classes to access the CMS DND extensions.
Create sources and targets
Define drag sources and drop targets to control which items a user drags and where they land. The following example implements both a drag source and a drop target in a single component.
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
DND data types act as contracts between drag sources and drop targets. Assign types to define which sources and targets are compatible. CMS uses the following DND types:
epi.cms.pagereference
epi.cms.blockreference
epi.cms.folderreference
epi.cms.contentreference– A string representation of a content reference, such as3_122. Converters convert specific types (page references) to content references. A drop target that accepts content references also accepts page references, but not the reverse.fileurl– A URL to a 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(specific versions includeepi.cms.page.lighturi) – An object with two properties:name– The name of the page.uri– The string representation of the content URI. The URI optionally includes version information, such asepi.cms.contentdata:///8.
epi.cms.content.light(specific versions includeepi.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 as123.
Type converters
Type converters transform dragged data from one format to another. Use converters when a drag source and drop target expect different object shapes. Converters also help when the source code of the drag source or drop target is not under your control. Specify types as an array for both the source and the target. Each array declares all types the component delivers or accepts.
- For a target, convert the data on acceptance.
- For a source, provide all supported types when the object shape is compatible.
Example scenario
The following scenario demonstrates how a type converter resolves a format mismatch between an image URL source and a link target.
A DND source delivers image URLs and specifies the type as ["imageurl", "fileurl"]. Both types expect a string with the URL. This data drags to targets that accept "imageurl" or "fileurl". A target that accepts "link" requires a different shape: an object with url and text properties. The source cannot produce both a string and a complex object at the same time.
Type converters handle this mismatch. Each converter implements a convert method with sourceDataType, targetDataType, and data parameters. Register converters through ObjectConverterRegistry in the module initialization method:
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 converts between content DND formats. In a standard installation, this converter enables dragging pages from the page tree to a page selector or a TinyMCE editor.
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
};
});
}
}
});
}
);Updated 17 days ago
