Extending Episerver On Page Editing view

D e v e l o p m e n t , E p i s e r v e r
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);
                }
            }
        });
    });