We have a lot of list properties in my current project. And most of them are edited entirely in the forms view which means that the editors are not able to preview the chosen images before publishing.
I decided to give it a try and find a way to somehow inject the thumbnails to the built-in Content Area editor.
The component extends built-in Content Area editor so all features of native widget like D&D, sorting and editing are available. Property backing type is still of type ContentArea, so there will be no problems with link validation and content migration to new EPiServer versions.
For now the property supports images from media assets, but it’s not difficult to implement thumbnails preview for custom blocks.
Implementing EditorDescriptor
The new descriptor simply derives from ContentAreaEditorDescriptor class and its only role is to change the client widget class to my custom implementation – “alloy.editors.contentAreaWithPreview”.
1 2 3 4 5 6 7 8 9 10 |
[EditorDescriptorRegistration (TargetType = typeof(ContentArea), UIHint = UiHint)] public class ContentAreaEditorWithPreviewDescriptor : ContentAreaEditorDescriptor { public const string UiHint = "content-area-with-preview"; public ContentAreaEditorWithPreviewDescriptor() { this.ClientEditingClass = "alloy.editors.contentAreaWithPreview"; } } |
Extending dojo widget
The extension point in Dojo widget is _craeteNode method of _ContentAreaTree widget. After calling the base _craeteNode method using inherited function, I’m modifying the content item to enable new functionality. Using this construction original _createTreeNode method of _ContentAreaTree class is executed before running custom code.
1 2 3 4 5 6 |
var _createTreeNode = this.tree._createTreeNode; this.tree._createTreeNode = lang.hitch(this.tree, function(model) { var node = _createTreeNode.call(this, model); self._modifyNode.call(self, node, model); return node; }); |
Thumbnail and preview URL fields are not available while adding a new item to the Content Area, but still we do have the access to the content ID.
To get the content together with properties we could use “epi.storeregistry” registry.
1 2 |
var registry = dependency.resolve("epi.storeregistry"); var store = registry.get("epi.cms.content.light"); |
TooltipDialog preview
I didn’t want to use large thumbnails inside ContentArea because a component with many images could be too high and it could be difficult to sort its items. That’s why I decided to show image preview as tooltip. It’s implemented using dijit TooltipDialog widget.
1 2 3 4 5 6 7 |
node.imageTooltip = new TooltipDialog({ connectId: [node.id], content: createHtml(), onMouseLeave: function() { popup.close(node.imageTooltip); } }); |
TooltipDialog widget doesn’t have open or close methods by itself. To show tooltip we have to use a popup helper from dijit controls.
1 2 3 4 5 6 7 |
on(imgNode, 'click', function() { popup.open({ popup: node.imageTooltip, around: dom.byId(node.id), orient: ["after-centered"] }); }); |
Using editor
You need to copy two code snippets to install the component:
- EditorDescriptor
- Dojo widget.
To use the editor add the UIHint to ContentArea property.
1 2 |
[UIHint(ContentAreaEditorWithPreviewDescriptor.UiHint)] public virtual ContentArea ContentAreaWithThumbnails { get; set; } |
Property was developed and tested in EPiServer 8.
Below you can find full widget code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
define([ "dojo/_base/array", "dojo/_base/connect", "dojo/_base/declare", "dojo/_base/lang", "dojo/query", "dojo/dom-class", "dojo/on", "dojo/dom", "dijit/popup", "dijit/TooltipDialog", "epi/epi", "epi/dependency", "epi-cms/contentediting/editors/ContentAreaEditor" ], function( array, connect, declare, lang, query, domClass, on, dom, popup, TooltipDialog, epi, dependency, _ContentAreaEditor ) { return declare("alloy.editors.contentAreaWithPreview", [_ContentAreaEditor], { buildRendering: function() { this.inherited(arguments); var self = this; // override _createTreeNode method var _createTreeNode = this.tree._createTreeNode; this.tree._createTreeNode = lang.hitch(this.tree, function(model) { var node = _createTreeNode.call(this, model); self._modifyNode.call(self, node, model); return node; }); }, _modifyNode: function(node, model) { var imgNode = query("img.dijitIcon", node.domNode); if (imgNode == null || imgNode.length == 0) { return; } var spanLabel = query("span.dijitTreeLabel", node.domNode); if (spanLabel == null || spanLabel.length == 0) { return; } this._resolveContentData(model.item.contentLink, lang.hitch(this, function(content) { if (!content.thumbnailUrl) { return; } // setup image imgNode = imgNode[0]; domClass.add(imgNode, "epi-thumbnail"); domClass.remove(imgNode, "epi-iconObjectImage"); domClass.remove(imgNode, "dijitTreeIcon"); imgNode.src = content.thumbnailUrl; // setup span spanLabel = spanLabel[0]; domClass.remove(spanLabel, "dijitTreeLabel"); domClass.add(spanLabel, "dojoxEllipsis"); // preview on dblclick on(imgNode, 'dblclick', function() { window.open(content.previewUrl, '_blank'); }); this._createTooltip(node, imgNode, content.previewUrl); })); }, _resolveContentData: function(contentlink, callback) { var registry = dependency.resolve("epi.storeregistry"); var store = registry.get("epi.cms.content.light"); if (!contentlink) { return null; } var contentData; dojo.when(store.get(contentlink), function(returnValue) { contentData = returnValue; callback(contentData); }); return contentData; }, _createTooltip: function (node, imgNode, previewUrl) { var createHtml = function() { return "<img src='" + previewUrl + "' style='max-height: 250px;max-width: 400px;'/>"; }; node.imageTooltip = new TooltipDialog({ connectId: [node.id], content: createHtml(), onMouseLeave: function() { popup.close(node.imageTooltip); } }); on(imgNode, 'mouseleave', function() { popup.close(node.imageTooltip); }); on(imgNode, 'click', function() { popup.open({ popup: node.imageTooltip, around: dom.byId(node.id), orient: ["after-centered"] }); }); } }); }); |