Editing user interface
Describes the architecture and different components of the editing user interface in Optimizely.
The user interface lets you plug your gadgets into the panels to extend editing possibilities.
The UI framework has a server-side part based on ASP.NET MVC and a client-side part using the JavaScript library Dojo. The UI also has context awareness, where a component can automatically reload when the context is changed or display customized content for that item.
You can edit content in the user interface in the following ways:
- On-page editing – Opens an editor directly on the page to edit simple property types such as short strings, integers, and decimal numbers.
- Forms editing – Edit properties in a form-based editing view. Here, you can edit properties that are not visible on the page.
This means the editor can make changes using different modes without losing context.
Autosave
Changes are automatically saved to the client state and sent to the server to be persisted. To reduce the server's burden, client and server synchronization occurs after a few seconds of delay. When a user edits a property or content block, the client creates and stores an undo step in a history that lets the user undo and redo changes. The undo and redo steps are available while the user remains editing the page and are lost when the user leaves the page. Any editing changes are sent to the server and are saved even if the user leaves the page or closes the browser.
Architecture
Editing components are organized into two layers.
- UI layer – Most Optimizely scripts and CSS files are loaded, and most of the user interface and interaction occur on this layer.
- Content page layer – This layer is inside an iframe and should have a few differences compared to the page shown to site visitors. This layer contains HTML5 data attributes injected in edit view to identify editable blocks. Also, a custom stylesheet is injected into the content pages when editing is enabled, but no other scripts or markup are injected; however, you can choose to have the epi object injected (see Loading the "epi" communication object). When the content page layer is loaded, the UI layer scans the content to find editable nodes and adds event handlers that trigger the loading of the correct editor.
You can enable editing for a specific area by adding the following attribute to the HTML tag data-epi-property-name="PageName"
:
<h1 data-epi-property-name="PageName">
<%= CurrentPage.PageName %>
</h1>
Clicking on a node in the following example loads whichever editor is configured for the Pagename
property.
A property can display several times on a page so that you can edit it in multiple places. Editing a property in one place updates any other place where the property is used. You can prevent a property from being edited by turning it off, (but the property updates its content if edited elsewhere).
Configuration of editors is done separately and does not have to be added to the HTML, even if there are a few other data attributes you can use to override default behavior.
When an editor is triggered for a property, an editor factory class creates the editor based on the data attributes and metadata for the property. Optimizely Framework extracts metadata from the following sources to create an editor.
- Extracting metadata attributes from the model class
- Global editor descriptors that can set up rules for a common language runtime type.
- Custom metadata providers that supply the metadata.
Note
For a description of the Optimizely object editing system, see Editing objects.
You can create page properties from typed model classes and in the administrative user interface. The PageData
class assembles metadata using a custom metadata provider that sends the metadata to the client using a RESTful service and is a hierarchical representation of the page and its properties. The following example shows how a property might be described:
modelType
 – the name of the CLR type, including its namespace.uiType
– the default client-side editor widget.customEditorSettings
– might have information about a non-standard editor type like an inline editor.settings
– a settings object passed to the editor widget's constructor.
{
"name" : "PageName",
"modelType" : "EPiServer.Core.PropertyString",
"uiType" : "dijit.form.ValidationTextBox",
"uiPackage" : null,
"customEditorSettings" : {
"uiWrapperType" : "inline",
"uiType" : "epi.cms.widget.ValidationTextBox"},
"layoutType" : null,
"defaultValue" : null,
"displayName" : "Name",
"groupName" : null,
"displayOrder" : -81,
"settings" : {
"label" : "Name",
"required" : true,
"missingMessage" : "The Name field is required.",
"invalidMessage" : "The field Name must be a string with a maximum length of 255.",
"regEx" : "^.{0,255}$",
"maxLength" : 255
},
"additionalValues" : {},
"properties" : [],
"groups" : null,'
"selections" : null
}
Server communication
The page editing component does the following tasks:
- Creates editable blocks for each node in the page with data attributes.
- Stores page data in the page model.
- Syncs changes using the page model server sync class.
The editable blocks are responsible for editing property data and using a render manager to render property on the page (on the client or server-side). Server-side rendering uses a queue so that properties are not rendered simultaneously. Changing one property can mean several properties on the page must be rendered again with different settings.
Editable blocks
Clicking on an editable block creates the editor and an editor wrapper for the block. The editor factory decides which wrapper and editor to use depending on the data attributes for editing and metadata for the property. You can connect to an event in the editor factory to change the values at runtime.
Data attributes have higher precedence than the metadata. You can use an editor for a property on a page that is not the standard editor for that property. You must use an editor designed for inline editing; the editor is a widget in other cases.
In MVC, you can pass the values in as anonymous objects to PropertyFor
helper method. You should pass in the render settings using parameter additionalViewData
 while you can pass in the editor settings using parameter editorSettings
.
These are the RenderSettings
for the PropertyFor
anonymous object:
Data property | Type | Default | Description |
---|---|---|---|
CustomTag | String | "div" | When in edit view, PropertyFor renders a wrapping element. This property decides the type. |
CssClass | String | empty string | The classes to be added to the wrapping element. (See above) |
ChildrenCustomTagName | String | "div" | Content areas render each child with a wrapping element. This property decides the type. |
ChildrenCssClass | String | empty string | The classes on the child wrapping elements. (See above) |
EditContainerClass | String | "epi-editContainer" | The default class sets minimum editing width and height. |
An example of customizing the rendering of string would be:
<p>This article originated in
@Html.PropertyFor(x => x.CurrentPage.CountryOfOrigin, new { CustomTag = "span"}).
</p>
Edit data attributes
Data attribute | Type | Required | Description |
---|---|---|---|
data-epi-edit | String | Yes (see note) | Equivalent to setting data-epi-property-name with the same value, data-epi-property-render="none", and data-epi-property-edittype="floating". |
data-epi-property-name | String | Yes | Name of the property. |
data-epi-property-render | String | No | Type of renderer to use. Possible values are: client (default), none , or and a custom value (for example my/CustomRenderer , see documentation). |
data-epi-property-edittype  | String | No | Type of wrapper to display editor in. Possible values are: floating (default), flyout , inline , webcontrol . Inline editing is only possible with custom inline editing widgets. Dijit widgets cannot be used inline. |
data-epi-property-editorsettings | JSON | No | Any settings for the editor. |
data-epi-property-rendersettings | JSON | No | Settings used to specify rendering, for example, custom tag. |
data-epi-property-editor | String | No | Type name of property editor widget. |
data-epi-use-mvc | Boolean | Yes | Selects the property renderer. Should be True when using ASP.NET MVC. Note: Not needed when setting data-epi-property-render="none" . |
Edit data attributes for Blocks in Content Areas
When rendering a content area, each block needs to have the data-epi-block-id
or data-epi-content-id
attribute on it for the block to be editable in the content area.
Data attribute | Type | Required | Description |
---|---|---|---|
data-epi-block-id | String | Yes | ContentLink ID of the content area item. |
data-epi-content-id | GUID | Yes | ContentLink GUID of the content area item |
Edit data events
When a property value is saved, a message is published on the topic contentSaved
, together with an object containing the content link to the content that was just updated. You can listen to this event with:
// Wait for the scripts to run so we can use the 'epi' global object.
window.addEventListener("load", function () {
epi.subscribe("contentSaved",
function (event) {
console.log("On Page Edited!");
console.log(event.contentLink);
});
});
The event will look like this.
{
"contentLink": "6_164",
}
Refresh OPE overlays
If the DOM changes so that there are new elements with data-epi-property-name
, or existing elements change their value to another property name, then the overlays automatically update themselves.
Edit context on the client
It may be necessary to know the current editing context of the client. The epi communication object now contains the following properties:
inEditMode
– True in edit mode and preview mode.isEditable
– True in edit mode and false in preview mode.ready
– True if the propertyisEditable
is initialized. Otherwise, subscribe toÂepiReady
 to get those properties when they are available.
const context = {
inEditMode: false,
isEditable: false
};
// Listen to the `epiReady` event to update the `context` property.
window.addEventListener('load', () => {
// Expect `epi` to be there after the `load` event. If it's not then we're
// not in any editing context.
if (!window.epi) {
return;
}
function setContext() {
// The event only has `isEditable`, but the epi object has both.
context.inEditMode = window.epi.inEditMode;
context.isEditable = window.epi.isEditable;
}
// Check that ready is an actual true value (not just truthy).
if (window.epi.ready === true) {
// `epiReady` already fired.
setContext();
// The subscribe method won't be available in View mode.
} else if (window.epi.subscribe) {
// Subscribe if the `epiReady` event hasn't happened yet.
window.epi.subscribe('epiReady', () => setContext());
}
});
Load the "epi" communication object
To get the "epi" object, you must have the attribute [RequireClientResources]
on your controller, unless you're already inheriting from PageController
or ContentController
.
Then require the resources in your razor view where you include any other scripts: @Html.RequiredClientResources("Footer")
Architecture diagram
Updated about 1 month ago