As we know EPiServer edit mode components, both built-in and custom, are implemented in a modular way. It means that they are isolated to each other. Yet when developing new features sometimes there is a need of creating dependencies between the widgets. It’s necessary when changing state of one widget should affect state of another one. For example when our component depends on current context. When editor selects a node from three structure the component should be notified about this event and take a proper action.
In many cases there is no easy way to get access from one widget to another, because there are no global repositories or the components work in a different scopes. Fortunately framework extensively use Dojo notification system. It’s implemented with “dojo/topic” module. After adding this dependency to our widget we get access to the topic instance. It has two public methods:
- publish – used for publishing a topic together with event arguments
- subscribe – used to subscribe to a topic
This is the implementation of publish/subscribe design pattern. The publisher broadcast a topic without the knowledge about receivers. The receivers subscribe to a topic, but without knowledge about publishers. With simple scenario it looks like:
One topic can be handled by many subscribers (or by nobody in edge case). Also the subscriber can be attached to the topic dynamically:
There a three topics described as public in the documentation:
- /epi/cms/requestcontextchange – the content context is changing
- /epi/cms/contextchanged – the content context already changed
- /epi/cms/contextchangefailed – error occurred during changing the context
Creating custom topics
We are not limited to using system topics only and we could publish our own events. For example model has two properties: “Shop country” and “Shop office”. The offices should be filtered out by countries that they belongs to. Based on this it could be useful to have cascading dropdown lists where child widget depends on value selected in parent widget.
To solve the problem, the “country” combobox could publish topic with selected value whenever selected value changed. The “office” combobox should subscribe to this topic and refresh the available options list when receives the event.
The topic publishing and subscribing syntax is:
1 2 3 4 5 6 7 8 |
// subscribe topic.subscribe("some/topic", function(event){ // do action based on event argument - // var test = event.name; }); // publish topic.publish("some/topic", {name:"some event", ...}); |
While the topics doesn’t have to be registered we could setup it just by defining a unique key. It could be for example “cascadingDropdow/SelectionChanged”. Publishing topic in master combobox could be implemented as:
1 2 3 4 5 |
this.setValueAttr(value) { this._set("value", value) topic.publish("cascadingSelectionChanged", value); } |
and subscribing in nested component:
1 2 3 4 5 6 7 |
postMixingPropertis: function() { topic.subscribe("cascadingSelectionChanged", function(value) { this.reloadOptions(value); }); } |
System topics
There are many topics used across the whole EPiServer edit mode (I found about 50 when browsing source code). We should avoid publishing and subscribing private events. The implementation of components could change in future without our knowledge. Yet I think that it’s good to be familiar with a least some of them. It could be useful for problems diagnostics purpose. Below I made some effort to described some of interesting topics and areas where they are used.
Context events
/epi/shell/context/request,
/epi/shell/context/changed,
/epi/shell/context/requestfailed,
/epi/shell/context/updated,
/epi/shell/context/current,
/epi/shell/context/updateRequest
This group of events is used very often in several areas, because many components depend on current context and should be refreshed when context change.
Those events are published when changing the context (“epi‑cms/component/command/ChangeContext”), creating new content (“epi‑cms/contentediting/CreateContent”), displaying deleted items in trash (“epi-cms/component/command/ViewTrash”), viewing the content versions (“epi-cms/component/Versions”) and many other places.
They are subscribed by several listeners like content tree (“epi-cms/widget/ContentTreeStoreModel”), global menu (“epi/shell/widget/GlobalMenu”) and widget switcher (“epi/shell/widget/WidgetSwitcher”)
There is no need to directly use topic.subscribe to handle context events. Most of them are wrapped in “epi/shell/_ContextMixin” mixin. So you just need to add the dependency and implement events.
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 |
define([ //... "epi/shell/_ContextMixin" //... ], function( // ... _ContextMixin //... ) { return declare("alloy.components.customComponent", [/* other mixins */_ContextMixin], { constructor: function(){ dojo.when(this.getCurrentContext(), function(context){ // context was loaded console.log("context loaded", context); }); }, contextUpdated: function (ctx, callerData) { console.log("context updated", ctx); }, contextChangeFailed: function (ctx, callerData) { console.log("context change failed", ctx); }, contextChanged: function (ctx, callerData) { console.log("context chainged", ctx); } }); }); |
There is a good article about how to use context sensitive widgets here.
Content Data
/epi/cms/contentdata/childrenchanged,
/epi/cms/contentdata/updated,
/epi/cms/contentdata/restored,
/epi/cms/content/statuschange/
Those topics are directly related with pages, blocks and folders. The content data topics are send when content was published (“epi-cms/contentediting/command/Publish”), page was moved in content tree (“epi-cms/widget/ContentTreeStoreModel”) or after name of the new folder changed (“epi-cms/widget/viewmodel/HierarchicalListViewModel”). Also when the language settings dialog is closed (“epi-cms/contentediting/command/_LegacyDialogCommandBase”) or the content is restored from trash (“epi-cms/widget/viewmodel/TrashViewModel”). The “statuschange” event is published during creating new draft (“epi-cms/component/command/SetCommonDraft”).
Components subscribed to content data topics are content tree (“epi-cms/widget/ContentTreeStoreModel”), the context synchronizer (“epi-cms/ContextSynchronizer”), page compare viewmodel (“epi-cms/compare/viewmodels/CompareViewModel”) and language button (“epi-cms/widget/ViewLanguageButton”).
There is also a separated topic for emptying the trash /epi/cms/trash/empty. It’s published in trash viewmodel (“epi-cms/widget/viewmodel/TrashViewModel”)
Pinnable
/epi/layout/pinnable/navigation/toggle,
/epi/layout/pinnable/navigation/visibilitychanged,
/epi/layout/pinnable/tools/toggle,
/epi/layout/pinnable/propertyEditor/show,
/epi/layout/pinnable/” + this.id + “/visibilitychanged
The “../pinnable/..” topics relates to components that can be shown or hide using pin button.
Published when showing or hiding panel (“epi-cms/_SidePanelsToggleMixin”), toggling navigation panel (“epi-cms/component/command/GlobalToolbarCommandProvider”), when click on overlay item (“epi-cms/contentediting/EditingBase”) or when toggling panel with pin (“epi-cms/_SidePanelsToggleMixin”).
Subscribed by Global Toolbar when navigation changed visibility (“epi-cms/component/command/GlobalToolbarCommandProvider”) and when changing the pane visibility (“epi/shell/layout/PinnablePane”).
Actions
There is few events published when command actions attached to buttons and context menus are executed.
- /epi/cms/action/togglecreatemode, /epi/cms/action/createlocalasset – published when creating new content (“epi-cms/contentediting/CreateContent”) or when creating new folder (“epi-cms/widget/command/NewFolder”). Subscribed by context store model (“epi-cms/widget/ContextualContentForestStoreModel”)
- /epi/cms/action/save, /epi/cms/action/undo, /epi/cms/action/redo, /epi/cms/action/disableundoredoactions – published when send the current content for review (“epi-cms/contentediting/command/SendForReview”) and by edit toolbar (“epi-cms/contentediting/EditToolbar”). Subscribed by EditingBase (“epi-cms/contentediting/EditingBase”) to take an action on viewmodel
- /epi/cms/action/viewsettingvaluechanged – used when a view setting value changed, like resolution or channel (“epi-cms/contentediting/viewmodel/DeviceSelectionSettingsModel”, “epi-cms/widget/ChannelsButton”), when language value changed (“epi-cms/contentediting/viewmodel/LanguageSettingsModel”),
when project changed (“epi-cms/project/ProjectPreviewButton”), when visitor group was selected (“epi-cms/visitorgroups/command/VisitorGroupViewSettingsModel”, “epi-cms/widget/VisitorGroupButton”) and when language changed (“epi-cms/widget/ViewLanguageButton”). Subscribe for listening changes of view setting, to apply view setting. (“epi-cms/contentediting/ViewSettingsManager”) - /epi/cms/action/switcheditmode – published when switching edit mode by editor toolbar (“epi-cms/contentediting/EditToolbar”), and by OnPage editing (“epi-cms/contentediting/OnPageEditing”). Subscribed by compare settings model (“epi-cms/compare/command/CompareSettingsModel”) and page data controller (“epi-cms/contentediting/PageDataController”)
- /epi/cms/action/refreshmytasks – publish event to update “My Tasks” component (“epi-cms/contentediting/WorkflowTaskNotification”) and subscribed for refreshing list in tasks gadget (“epi-cms/component/Tasks”)
- /epi/cms/action/disablepreview – published when switching edit mode in page data controller(“epi-cms/contentediting/PageDataController”) and in the view selector (“epi-cms/widget/ViewSelector”). Subscribed for deactivating the option in “epi-cms/command/TogglePreviewMode”.
- /epi/cms/action/showerror – published when error occurred during publishing (“epi-cms/contentediting/AutoSaveButton”), during validation of edited content failed (“epi-cms/contentediting/ContentEditingValidator”) or when creating new content failed (“epi-cms/contentediting/CreateContent”)
- /epi/cms/action/delete/error – published when deleting content failed (“epi-cms/command/DeleteContent”)
- /epi/cms/action/eyetoggle – used when toggling the view settings active state (“epi-cms/command/ToggleViewSettings”, “epi-cms/widget/ViewSettingsExpandoButton”). Subscribed by view setting manager (“epi-cms/contentediting/ViewSettingsManager”) and in compare settings model (“epi-cms/compare/command/CompareSettingsModel”)
Dojo events related with D&D
/dnd/start
/dnd/source/over
/dnd/drop/before
/dnd/drop
/dnd/cancel
/dnd/move/start
/dnd/move/stop
/epi/dnd/dropdata
/EPi/DnD/DragStart
/EPi/DnD/DragEnd
Used whenever using D&D functionality. For example by ContentArea when inserting elements from blocks three. Usually there is no need to use them directly because they are wrapped in EPiServer modules responsible for drag&drop: “epi/dnd/Source”, “epi/dnd/Target”, “epi/shell/dnd/Source”, “epi/shell/dnd/Target”.
Visitor groups
this.id+”-startup”,
this.id+”-selectChild”,
this.id+”-addChild”,
this.id + “-removeChild”,
this.id+”-containerKeyPress”
Visitor groups widget (“epi/layers/visitorgroup-widgets”) publish topics subscribed by Stack Controller (“dijit/layout/StackController”). All operations on children elements, like adding, selecting deleting are published as a topic and then handled by stack controller.
Changing view
/epi/shell/action/changeview,
/epi/shell/action/changeview/deactivate,
/epi/shell/action/changeview/updatestate,
/epi/shell/action/changeview/back
The changeview topic is used for instance when switching between On Page edit and Forms Editing.
It’s published when creating new content (“epi-cms/command/NewContent”), translating content (“epi‑cms/command/TranslateContent”), comparing settings (“epi-cms/compare/command/CompareSettingsModel”) or when preview mode active state changed (“epi-cms/command/TogglePreviewMode”).
They are consumed by widget switcher (“epi/shell/widget/WidgetSwitcher”), pinnable pane (“epi/shell/layout/PinnablePane”) and by floating editor (“epi-cms/contentediting/FloatingEditorWrapper”)
Application
Application level topics could affects several widgets. They are published when the layout of the edit mode changed.
- /epi/shell/globalnavigation/layoutchange – notify that the root container to re-layout (used in “epi/shell/widget/LicenseInformation”)
- /epi/shell/application/initialized – published when framework is initialized and running (epi/main, “epi/shell/Bootstrapper”). Subscribed in Shell module (“epi/shell/ShellModule”)
- /site/checksize – published when need to check window size (communicationInjector.js, “epi/shell/widget/_AutoResizingIframeMixin” )
Tracking events in console
I prepared an example of how to simply subscribe to system topics. The code contains the listener attached to all events described above. It based on two files: modules.config and initialization script
The modules.config has initialization file path and dependency on “epi-cms.widgets.base”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?xml version="1.0" encoding="utf-8"?> <module> <clientResources> <add dependency="epi-cms.widgets.base" path="Scripts/eventSamples/eventsLogger.js" resourceType="Script" /> </clientResources> <clientModule initializer="events.eventSamples.eventsLogger"> <moduleDependencies> <add dependency="CMS" type="RunAfter" /> </moduleDependencies> </clientModule> <dojo> <paths> <add name="events" path="Scripts" /> </paths> </dojo> </module> |
… and the initialization script itself
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 |
define([ "dojo", "dojo/_base/declare", "epi/_Module", "dojo/topic" ], function( dojo, declare, _Module, topic ) { return declare([_Module], { initialize: function() { this.inherited(arguments); var events = []; var contextArray = ["/epi/shell/context/changed", "/epi/shell/context/requestfailed", "/epi/shell/context/updated", "/epi/shell/context/current", "/epi/shell/context/request", "/epi/shell/context/updateRequest" ]; events= events.concat(contextArray); var contentDataArray = ["/epi/cms/contentdata/childrenchanged", "/epi/cms/contentdata/updated", "/epi/cms/contentdata/restored", "/epi/cms/content/statuschange/"]; events = events.concat(contentDataArray); var changeViewArray = ["/epi/shell/action/changeview", "/epi/shell/action/changeview/deactivate", "/epi/shell/action/changeview/updatestate", "/epi/shell/action/changeview/back"]; events = events.concat(changeViewArray); var actionsArray = ["/epi/cms/action/showerror", "/epi/cms/action/delete/error", "/epi/cms/action/eyetoggle", "/epi/cms/action/createlocalasset", "/epi/cms/action/togglecreatemode", "/epi/cms/action/save", "/epi/cms/action/undo", "/epi/cms/action/redo", "/epi/cms/action/disableundoredoactions", "/epi/cms/action/switcheditmode", "/epi/cms/action/disablepreview", "/epi/cms/action/refreshmytasks", "/epi/cms/action/viewsettingvaluechanged"]; events = events.concat(actionsArray); var pinnableArray = ["/epi/layout/pinnable/navigation/toggle", "/epi/layout/pinnable/navigation/visibilitychanged", "/epi/layout/pinnable/tools/toggle", "/epi/layout/pinnable/propertyEditor/show" ]; events = events.concat(pinnableArray); var dragdropArray = [ "/dnd/start", "/dnd/source/over", "/dnd/drop/before", "/dnd/drop", "/dnd/cancel", "/dnd/move/start", "/dnd/move/stop", "/epi/dnd/dropdata ", "/EPi/DnD/DragStart", "/EPi/DnD/DragEnd" ]; // there is al ot of D&D events published - temporary turned off //events= events.concat(dragdropArray); var othersArray = ["/epi/shell/globalnavigation/layoutchange", "/epi/shell/application/initialized", "/epi/cms/trash/empty", "/site/checksize"]; events = events.concat(othersArray); function logEvent(eventName) { topic.subscribe(eventName, function () { console.log(eventName); }); }; for (var i = 0; i < events.length; i++) { logEvent(events[i]); } } }); }); |
The structure of files in project:
And the video showing how many events are published in just few seconds.
Below I included the result of those few seconds of tracing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/epi/layout/pinnable/navigation/visibilitychanged eventsLogger.js:85 /epi/layout/pinnable/navigation/visibilitychanged eventsLogger.js:85 /epi/layout/pinnable/navigation/toggle eventsLogger.js:85 /epi/layout/pinnable/navigation/visibilitychanged eventsLogger.js:85 /epi/layout/pinnable/navigation/visibilitychanged eventsLogger.js:85 /epi/shell/context/request eventsLogger.js:85 /epi/shell/context/changed eventsLogger.js:85 /epi/shell/action/changeview/updatestate eventsLogger.js:85 /epi/shell/context/current eventsLogger.js:85 /epi/cms/content/statuschange/ eventsLogger.js:85 /epi/shell/context/request eventsLogger.js:85 /epi/cms/contentdata/childrenchanged eventsLogger.js:85 /epi/shell/context/changed eventsLogger.js:85 /epi/shell/action/changeview/updatestate eventsLogger.js:85 /epi/shell/context/current eventsLogger.js:85 /epi/cms/action/switcheditmode eventsLogger.js:85 /epi/shell/action/changeview/updatestate eventsLogger.js:85 /epi/shell/context/current eventsLogger.js:85 /epi/shell/context/current eventsLogger.js:85 /epi/cms/action/switcheditmode eventsLogger.js:85 /epi/shell/action/changeview/updatestate eventsLogger.js:85 /epi/shell/context/current |
The code is available on Gists