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 as3_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 resourcetext
– the inner text for the link
epi.cms.content.lighturi
(there also are specific versions likeepi.cms.page.lighturi
) – An object with two properties:name
– The name of the pageuri
– The string representation of the content URI. This might or might not include version information such asepi.cms.contentdata:///8
.
epi.cms.content.light
(there also are specific versions likeepi.cms.page.light
) – An object with two properties:name
– The name of the pagecontentLink
– 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
};
});
}
}
});
}
);
Updated 9 months ago