Optimizely’s Generic PropertyLists can be a useful tool for both developers and CMS editors, but it does come with limitations.
Our client recently approached us asking for a better way to organize their PropertyLists. Some of their lists had grown to be quite long and having to move items one slot at a time was not an enjoyable experience.
There is an existing solution out there already for those with Commerce, but our client is CMS only.
A basic knowledge of dojo is recommended, but knowing dojo’s aspect module and the drag-and-drop system(DnD) is required to really understand how the solution works.
Issues
If you’re only interested in the solution, feel free to skip this section and go directly to it.
Surprisingly, I found that DnD is already set up, just not fully functioning. The PropertyList items can be dragged, just not dropped.
Debugging the existing code, I discovered the issue is that PropertyList type isn’t being added to the allowed DnD types.
In the collection editor code, allowedDndTypes is always undefined for PropertyLists.
epi-cms/contentediting/editors/CollectionEditor.js
In the DnD code to check if a drop is allowed, the acceptedTypes for PropertytLists is always set to text, which is just the default value.
epi/shell/dnd/_DndDataMixin.js
dojo/dnd/Source.js
Solution
In order to get around this, I extended the base CollectionEditor dojo widget. This solution was tested in both CMS 11 and CMS 12.
Thanks to dojo’s aspect module, we can intercept the DnD call chain where needed instead of trying to force the entire call chain to work with PropertyLists.
Here, I intercepted the problematic call to DnD’s _checkAcceptanceForItems() in order to discard its return value and return my own custom implementation. The key part is line 35, where we check the source items against the PropertyList type(self.itemType).
define('custom-scripts/Editors/PropertyListCollectionEditor', [ // dojo core 'dojo/_base/declare', // Used to declare the actual widget 'dojo/_base/array', 'dojo/aspect', // Optimizely 'epi-cms/contentediting/editors/CollectionEditor', // Opti base widget to extend, ], function ( declare, array, aspect, CollectionEditor) { return declare([CollectionEditor], { _setupDnD: function () { // summary: // Set up the dnd on the grid. // tags: // private this.inherited(arguments); var self = this; self.own( aspect.after( self.grid.dndSource, // Target '_checkAcceptanceForItems', // Target's method to watch function (items, acceptedTypes) { // Run after target's method & // replace original return value with logic for PropertyLists return array.every(items, (item) => { return self.itemType.toLowerCase() === item.data.typeIdentifier; }); }, true, // Receive target method's original arguments ), ); }, }); });
After that, I created an editor descriptor to use the new widget as its editing class.
public class PropertyListEditorDescriptor<T> : CollectionEditorDescriptor<T> where T : new() { public PropertyListEditorDescriptor() { ClientEditingClass = "custom-scripts/Editors/PropertyListCollectionEditor"; } }
Now to enable drag and drop support, I only need to use the PropertyListEditorDescriptor in place of CollectionEditorDescriptor when setting up PropertyLists.
[EditorDescriptor(EditorDescriptorType = typeof(CollectionEditorDescriptor<Location>))] public virtual IList<Location> Locations { get; set; }