Extending Episerver On Page Editing view

Mattias Olsson 24.01.2018 16.07.18

Specification

All images that are uploaded to Episerver is an instance of the ImageFile content type which in this example looks like this. As you can see I have two additional properties to set default caption and alt text:

public class ImageFile : ImageData
{
    public virtual string DefaultCaption { get; set; }

    public virtual string DefaultAltText { get; set; }
}

On article pages, I have a ContentReference property to add an image to the article. I also have properties for image caption and image alt text. This is my article content type:

public class ArticlePage : PageData
{
    [UIHint(UIHint.Image)]
    public virtual ContentReference MainImageLink { get; set; }

    [CultureSpecific]
    public virtual string MainImageCaption { get; set; }

    [CultureSpecific]
    public virtual string MainImageAltText { get; set; }
}

Now, what I want to accomplish with this is that when a web editor selects an image on an article, I want the MainImageCaption and MainImageAltText property values to be automatically copied from the DefaultCaption and DefaultAltText properties on the selected ImageFile.

Code

First of all we need to create a custom ViewConfiguration for ArticlePage content type. The differences from the built-in page view configuration is underlined.

[ServiceConfiguration(typeof (ViewConfiguration))]
public class ArticleOnPageEditing : ViewConfiguration<ArticlePage>
{
    public ArticleOnPageEditing()
    {
        Key = "article-onpageedit";
        LanguagePath = "/episerver/cms/contentediting/views/onpageediting";
        ControllerType = "epi-cms/contentediting/PageDataController";
        ViewType = "alloy/contentediting/ArticleOnPageEditing";
        IconClass = "epi-iconLayout";
        SortOrder = 100;
    }
}

Next thing is to create a UI descriptor to set our custom view as default.

[UIDescriptorRegistration]
public class ArticleUIDescriptor : PageUIDescriptor
{
    public ArticleUIDescriptor() : base()
    {
        ForType = typeof(ArticlePage);
        TypeIdentifier = ForType.FullName.ToLowerInvariant();
        DefaultView = "article-onpageedit";
        AddDisabledView(CmsViewNames.OnPageEditView);
    }
}

Last but not least, we have to create the dojo part of our new view. To make it easy, we inherit the built-in OnPageEditing view. To detect changes to properties I have chosen to override the method _onWrapperValueChange.

define([
    'dojo/_base/declare',
    'dojo/_base/lang',
    'dojo/when',
    'epi/dependency',
    'epi-cms/contentediting/OnPageEditing'
],
    function (
        declare,
        lang,
        when,
        dependency,
        OnPageEditing
    ) {
        return declare('alloy/contentediting/ArticleOnPageEditing', [OnPageEditing], {
            _imagePropertyName: 'mainImageLink',
            _captionTextPropertyName: 'mainImageCaption',
            _altTextPropertyName: 'mainImageAltText',
            _sourceCaptionTextPropertyName: 'defaultCaption',
            _sourceAltTextPropertyName: 'defaultAltText',
            _storeName: 'epi.cms.contentdata',
            store: null,

            constructor: function() {
                this.inherited(arguments);
                this.store = this.store || dependency
                    .resolve("epi.storeregistry")
                    .get(this._storeName);
            },

            _onWrapperValueChange: function (wrapper, value, oldValue) {
                this.inherited(arguments);

                if (wrapper.propertyName === this._imagePropertyName) {
                    if (value) {
                        // When an image is selected, fetch content data from content data store.
                        when(
                            this.store.get(value), 
                            lang.hitch(this, this._onImageContentLoaded)
                        );
                    }
                }
            },

            _onImageContentLoaded: function(contentData) {
                this._setImageTextFromImageContent(contentData);
            },

            _setImageTextFromImageContent: function(contentData) {
                var captionTextProperty = this._mappingManager.findOne(
                    'propertyName', 
                    this._captionTextPropertyName
                );

                var defaultCaptionValue =
                    contentData.properties[this._sourceCaptionTextPropertyName];

                if (captionTextProperty && defaultCaptionValue) {
                    this._setPropertyValueIfEmpty(
                        this._captionTextPropertyName, 
                        defaultCaptionValue
                    );
                }

                var altTextProperty = this._mappingManager.findOne(
                    'propertyName', 
                    this._altTextPropertyName
                );

                var defaultAltTextValue = 
                    contentData.properties[this._sourceAltTextPropertyName];

                if (altTextProperty && defaultAltTextValue) {
                    this._setPropertyValueIfEmpty(
                        this._altTextPropertyName, 
                        defaultAltTextValue
                    );
                }
            },

            _setPropertyValueIfEmpty(propertyName, propertyValue) {
                var val = this.viewModel.getProperty(propertyName);

                // We only want to set the value if it's empty.
                if (!val) {
                    this.viewModel.setProperty(propertyName, propertyValue, val);
                }
            }
        });
    });