Episerver gives a flexible way of adding and rearranging gadgets in edit mode. Editors can select which gadgets should be displayed, change their position and size. But for some editing scenarios we would like to force specific gadgets to be displayed for all editors and behave similar to the “Page tree”. Those fixed gadgets should should be added automatically when Editor logs-in to the edit mode and it should not be possible to remove them. I prepared a small snippet that allow you to make this work.
Creating an example
First we need a simple gadget. It won’t have a logic inside and just display a static text. We need to register it and add client class.
Registration
To register new gadget I created “AlloyComponent” class that inherits from ComponentDefinitionBase and added Component attribute. Then I set client widget to “alloy/AlloyComponent“.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using EPiServer.Shell.ViewComposition; namespace AlloyMvcTemplates.CustomComponent { [Component] public class AlloyComponent : ComponentDefinitionBase { public AlloyComponent() : base("alloy/AlloyComponent") { Title = "Test Component"; //Categories = new[] {"cms"}; } } } |
The “Category” property defines group name where gadget should appear. It can be uncommented for now to test if gadget works. But later, when we make gadget fixed, the Category property should be removed and gadget should not be selectable by Editor
Implementing client widget
Widget has template with a text displayed in a DIV element.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
define([ "dojo/_base/declare", "dijit/_Widget", "dijit/_TemplatedMixin" ], function ( declare, _Widget, _TemplatedMixin ) { return declare([_Widget, _TemplatedMixin], { templateString: "<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse at quam vehicula, venenatis sem quis, tempor massa. Phasellus non neque placerat, varius ligula sit amet, rhoncus nibh.</div>" }); }); |
The “AlloyComponent.js” file should be added to “ClientResources/Scripts” directory.
After running the site and adding gadget to edit mode it will looks like:
Automatic registration
To automatically add gadget to the sidebar we could create new implementation of IViewTransformer and use “AddAndSave” container method.
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 |
using System; using System.Linq; using System.Security.Principal; using EPiServer.Shell; using EPiServer.Shell.ViewComposition; namespace AlloyMvcTemplates.CustomComponent { [ViewTransformer] public class DefaultComponentsTransform : IViewTransformer { private readonly IComponentManager _manager; public DefaultComponentsTransform(IComponentManager manager) { _manager = manager; } public int SortOrder => 10001; public void TransformView(ICompositeView view, IPrincipal principal) { var container = view.RootContainer.FindContainerByPlugInArea(PlugInArea.Navigation); if (container == null) { return; } var exist = container.Components.Any(c => c.DefinitionName == typeof(AlloyComponent).FullName); if (!exist) { var definition = _manager.GetComponentDefinition(typeof(AlloyComponent).FullName); var component = definition.CreateComponent(); component.Id = new Guid("31B4F206-195E-482C-ACCF-450982916C72"); component.SortOrder = 500; container.AddAndSave(view, principal, component); } } } } |
The code above will check if AlloyComponent was added and if not, then add it to the Navigation area.
Now we have gadget that will be displayed for all editors, but it still can be manually removed by Editor. Of course, after refreshing the page, it will be added again (by ViewTransformer), but it would be better to make gadget fixed.
Fixed gadgets
I prepared “FixedComponent” dojo mixin. The mixin is responsible for disabling “Remove gadget” command and “X” button when rearranging gadgets. It can be used with any custom components, not only with this example. The full source code of the mixin:
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 |
define([ "dojo/_base/declare", "dojo/aspect", "epi/shell/widget/command/RemoveGadget" ], function ( declare, aspect, RemoveGadgetCommand ) { return declare([], { hideMenuBar: true, startup: function () { if (this._started) { return; } this.inherited(arguments); var componentChrome = this.getParent(); // component wrapper is parent of parent of component var componentWrapper = componentChrome.getParent(); // remove command should be available componentWrapper.commands.forEach(function (cmd) { if (cmd.isInstanceOf(RemoveGadgetCommand)) { cmd.set("isAvailable", false); } }); // if remove command is the only one command then we can hide whole menu bar if (this.hideMenuBar) { componentChrome.toolbar.domNode.classList.add("dijitHidden"); } // the "X" button should not be available aspect.around(componentWrapper, "_setClosableAttr", function (originalSetter) { return function () { originalSetter.call(componentWrapper, false); componentWrapper.set("dragRestriction", true); } }); } }); }); |
Mixin has also hideMenuBar flag that allows to hide the bottom menu bar. It can be useful when gadget has only one “Remove gadget” command and menu bar is not needed.
To use the snippet, mixin file should be simply copied to the solution and then used by component class. For AlloyComponent example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
define([ "dojo/_base/declare", "dijit/_Widget", "dijit/_TemplatedMixin", "./FixedComponentMixin" ], function ( declare, _Widget, _TemplatedMixin, FixedComponentMixin ) { return declare([_Widget, _TemplatedMixin, FixedComponentMixin], { templateString: "<div>Lorem ipsum dolor sit amet..</div>" }); }); |
As you can see I had to change only 3 lines of code: add dependency on “./FixedComponentMixin” (I copied the mixin to the same directory as AlloComponent.js) and add mixin to the declaration next to the _TemplatedMixin.
Now when running the site, the gadget can’t be removed.
And in rearranging gadgets scenario:
it also can’t be removed:
Fixed built-in gadgets
With few lines of code it’s possible to make built-in gadgets fixed.
For example we would like to have fixed Versions gadget.
We need to register new component with copy of all properties from built-in component, but with custom client editor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using EPiServer.Shell.ViewComposition; namespace AlloyMvcTemplates.CustomComponent { [Component] public class FixedVersionsComponent : ComponentDefinitionBase { public FixedVersionsComponent() : base("alloy/FixedVersionsComponent") { LanguagePath = "/episerver/cms/components/versions"; } } } |
Then client widget will be declared using two classes. First one will be built-in widget (in this example “epi-cms/component/VersionsComponent”) and second is FixedComponentMixin.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
define([ "dojo/_base/declare", "epi-cms/component/VersionsComponent", "./FixedComponentMixin" ], function ( declare, VersionsComponent, FixedComponentMixin ) { return declare([VersionsComponent, FixedComponentMixin], { hideMenuBar: false }); }); |
The Versions gadget has more menu commands, so hideMenuBar should be set to false.
Registration
Now we can extend CustomComponentsTransformer and register both AlloyComponent and FixedVersions gadgets:
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 |
using System; using System.Linq; using System.Security.Principal; using EPiServer.Shell; using EPiServer.Shell.ViewComposition; namespace AlloyMvcTemplates.CustomComponent { [ViewTransformer] public class CustomComponentsTransformer : IViewTransformer { private readonly IComponentManager _manager; public DefaultComponentsTransform(IComponentManager manager) { _manager = manager; } public int SortOrder => 10001; public void TransformView(ICompositeView view, IPrincipal principal) { void AddComponent(Type componentType, Guid id, int sortOrder) { var container = view.RootContainer.FindContainerByPlugInArea(PlugInArea.Navigation); if (container == null) { return; } var exist = container.Components.Any(c => c.DefinitionName == componentType.FullName); if (!exist) { var definition = _manager.GetComponentDefinition(componentType.FullName); var component = definition.CreateComponent(); component.Id = id; component.SortOrder = sortOrder; container.AddAndSave(view, principal, component); } } AddComponent(typeof(AlloyComponent), new Guid("31B4F206-195E-482C-ACCF-450982916C72"), 500); AddComponent(typeof(FixedVersionsComponent), new Guid("8A6A5BCE-DB59-46D5-952A-B00C61758424"), 501); } } } |