Posted in: Comments

I’m still on my quest for clean markup and a nice editor experience. Please see my previous post how about to handle wrapping links. In this post I will cover a way to add wrapper markup to properties, that is only rendered in edit mode or when the properties have values. Notice that this will work for multiple properties, since quite often we have markup that wraps more than one property. So why would we even bother about this, you might ask? Well, sometimes we have styling that will apply to the wrapping markup, that we don’t want to show if the properties doesn’t have a value.

I don’t think there is much to say about the code, it’s very self-explanatory, so here it comes:

namespace DV.WebControls
{
    using System;
    using System.ComponentModel;
    using System.Linq;
    using System.Web.UI;
    using System.Web.UI.WebControls;

    using EPiServer.Editor;
    using EPiServer.ServiceLocation;
    using EPiServer.Web;

    public enum EvaluationType
    {
        Any,
        All
    }

    /// <summary>
    ///     A web control that always renders all child controls in edit mode. In view mode all specified
    ///     properties' value is evaluated. If the evalutation is successful according to the specified EvaluationType,
    ///     all child controls are rendered. Otherwise no child controls are rendered.
    /// </summary>
    [ParseChildren(false)]
    [PersistChildren(true)]
    public class PropertyPlaceHolder : Control
    {
        /// <summary>
        ///     Initializes a new instance of the <see cref="PropertyPlaceHolder" /> class.
        /// </summary>
        public PropertyPlaceHolder()
        {
            this.EvaluationType = EvaluationType.Any;
        }

        /// <summary>
        ///     Gets or sets the type of the evaluation.
        /// </summary>
        public EvaluationType EvaluationType { get; set; }

        /// <summary>
        ///     Gets or sets the name of the property.
        /// </summary>
        [TypeConverter(typeof(StringArrayConverter))]
        public string[] PropertyName { get; set; }

        // ReSharper disable once UnusedAutoPropertyAccessor.Local
        private Injected<ControlRenderContextBuilder> ContextBuilder { get; set; }

        /// <summary>
        ///     Raises the <see cref="E:System.Web.UI.Control.Load" /> event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.EventArgs" /> object that contains the event data.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            if (this.Visible && this.PropertyName != null)
            {
                if (this.EvaluationType == EvaluationType.Any)
                {
                    this.Visible = this.PropertyName.Any(this.ShouldRenderControl);
                }

                if (this.EvaluationType == EvaluationType.All)
                {
                    this.Visible = this.PropertyName.All(this.ShouldRenderControl);
                }
            }
        }

        private bool ShouldRenderControl(string propertyName)
        {
            var propertyContext = this.ContextBuilder.Service.BuildContext(this, propertyName);

            if (PageEditing.PageIsInEditMode)
            {
                return propertyContext.IsEditable();
            }

            var property = propertyContext.PropertyContainer.Property[propertyName];

            // In view mode, we only need to check if the property
            // has a value or not.
            return property != null && property.IsNull == false;
        }
    }
}

Here are some examples on how we can use the control

In this first example we want to wrap a content area with teaser blocks:

<DV:PropertyPlaceHolder PropertyName="Teasers" runat="server">
	<div class="teaser-listing">
		<div class="wrapper">
			<EPiServer:Property PropertyName="Teasers" CssClass="m-cols-2" runat="server">
				<RenderSettings ChildrenCssClass="m-col" />
			</EPiServer:Property>
		</div>
	</div>
</DV:PropertyPlaceHolder>

In this second example we want to create a definition list of a bunch of contact information properties, notice that the dl element will get rendered if any of the specified properties have a value:

<DV:PropertyPlaceHolder PropertyName="Department,Office,Email,Telephone" EvaluationType="Any" runat="server">
	<dl>
		<DV:PropertyPlaceHolder PropertyName="Department" runat="server">
			<dt>Department</dt>
			<EPiServer:Property PropertyName="Department" CustomTagName="dd" runat="server" />
		</DV:PropertyPlaceHolder>

		<DV:PropertyPlaceHolder PropertyName="Office" runat="server">
			<dt>Office</dt>
			<EPiServer:Property PropertyName="Office" CustomTagName="dd" runat="server" />
		</DV:PropertyPlaceHolder>

		<DV:PropertyPlaceHolder PropertyName="Email" runat="server">
			<dt>Email</dt>
			<EPiServer:Property PropertyName="Email" CustomTagName="dd" runat="server" />
		</DV:PropertyPlaceHolder>

		<DV:PropertyPlaceHolder PropertyName="Telephone" runat="server">
			<dt>Telephone</dt>
			<EPiServer:Property PropertyName="Telephone" CustomTagName="dd" runat="server" />
		</DV:PropertyPlaceHolder>
	</dl>
</DV:PropertyPlaceHolder>