Pages

Thursday, April 19, 2018

Rendering Options for DateTime (and other) Properties in Episerver

The question has been brought up to me more than a couple times in the past few weeks about how to properly format a DateTime property in a View and still allow In-Page Editing to work. Since I have answered it with mostly the same information each time it has come up, I figured it was time to put something out in cyberspace that I can point people to, or even help them find when the situation arises.

I am a huge fan of In-Page Editing in Episerver, and part of my goal when teaching people techniques working with Epi is to keep that In-Page Editing experience in place for users and editors so they can get the most enjoyable experience out of their site and CMS.

The nice thing about the approaches in this article is that while the article is centered around the DateTime property, most of these techniques apply to any property where you need to display something different than the actual property value.

Ultimately, then, this article is about the various techniques to render a property in a view while keeping the In-Page Editor working. I'm just using a DateTime property as an example.

The Issue

Defining a property as a DateTime in Episerver is pretty straight forward. It gives you a Date picker calendar in the Properties view for a Block or Page, and displays the DateTime value in the Properties view according to the globalization settings for the current user. When you use the PropertyFor extension in a view, it also displays the DateTime value according to the user globalization settings.

The catch is: you cannot use a .ToString("[date format]") in your PropertyFor syntax.
[pre class="brush:razor;" title="This will not work and Episerver will yell at you"]
<p>Publication date</p>
@Html.PropertyFor(m=> m.CurrentPage.CreationDate2.ToString("MMMM d, yy"))
[/pre]












It's pretty easy to take that ToString approach, and slap it into a View to get your format the way you want it. But, if you do that, you sacrifice your In-Page Editing.
Notice the bottom date lacks the blue box
 indicating an editable section like the date above it has.
[pre class="brush:razor" title="Format achieved, but editing experience lost."]
@Model.CurrentPage.CreationDate.ToString("MMMM d, yy")
[/pre]

The Options

There are several ways to accomplish tasks like this in Episerver, and the approach you use depends on the implementation needs. Many times you will even combine approaches to best suit the needs of your users.

Option 1. DataFormatString

The System.ComponentModel.DataAnnotations namespace contains a DisplayFormatAttribute which is useful to specify the display and formatting, or special handling, of data fields. One of the properties supported is DataFormatString, which specifies the string formatting that should be used. For a DateTime property, this property allows you to specify a similar structure to what you use in the ToString method of the DateTime class. 

Since Episerver's PropertyFor method hands off the rendering to MVC's DisplayFor method, this approach works without anything more than using the Episerver PropertyFor method in your view. The nice thing about this approach is the format specified becomes the default anywhere the value of the property is rendered, but it can be overridden with any of the other approaches below, providing a nice fallback.
[pre class="brush:csharp"] [DisplayFormat(DataFormatString = "{0:MMMM dd, yyyy}")] public virtual DateTime CreationDate { get; set; } [/pre]

Option 2. EditAttributes

If you have a specific block of markup containing your DateTime property, or a small section you need a quick solution for, the EditAttributes helper allows you to create an In-Page edit section using the markup you already have in place. The EditAttributes helper is added to the wrapper element of the section and accepts an expression argument to indicate which property it supports. Any PropertyFor helpers within this area are ignored and the section becomes an edit area for the property specified without adding additional markup or changes to your view.

For the DateTime property, you can then use the ToString method to specify the desired format.
[pre class="brush:razor"]
<div class="date-heading-section" @Html.EditAttributes(m => m.CurrentPage.CreationDate)>
    Creation Date: @Model.CurrentPage.CreationDate.ToString("MMMM d, yy")
</div>
[/pre]

Option 3. EditSection

If you haven't used it before, the BeginEditSection helper is a very useful approach to become familiar with in Episerver. It allows you to create an editable area in a page or block view for a property where you need more complex markup than you might use the PropertyFor helper for. To implement the BeginEditSection, include arguments specifying a tag type, and an expression for the property it will support. You must also add an EndEditSection line, specifying the same tag type used for BeginEditSection. Everything in between the two helpers will be wrapped as an editable area on the page, which gives you great flexibility.
Like the EditAttributes approach, you can use the ToString method within the edit section to specify the format while still supporting In-Page Editing.
[pre class="brush:razor"]
@Html.BeginEditSection("div", m => m.CurrentPage.CreationDate)
    @Model.CurrentPage.CreationDate.ToString("MMMM d, yy")
@Html.EndEditSection("div")
[/pre]

Option 4. Display Template

While the EditAttributes and EditSections are great for areas that might not be reused, Display Templates are a better option if you need to use the same markup for your date format in various pages or blocks throughout the site. One advantage is you can create a default rendering for a property type by creating a Display Template with the same name as your property type, which allows you to use the PropertyFor method without any tags needed to render that template for any property matching the type. For a DateTime property, this gives you a clean way to render a formatted DateTime for any block or page view.
[pre class="brush:razor" title="DateTime.cshtml"]

@model DateTime

@Model.ToString("MM.dd.yyyy")

[/pre][pre class="brush:razor" title="Page markup"]
<div class="publication--date">
    <p>Article published on</p>
    <p>@Html.PropertyFor(m=> m.CurrentPage.CreationDate)</p>
</div>
[/pre]

In addition to matching the Display Template name to the property type name, you can utilize Tags on the PropertyFor method to indicate additional renderings. For the DateTime example, this means you can create multiple Display Templates for different formats needed throughout your blocks and pages.

The image on the left shows the various date formats I defined templates for. Adding a Tag matching a Display Template name in the "additionalViewData" argument of the PropertyFor method will cause that view to be used.
[pre class="brush:razor" title="Reference a Display Template"]
<p>Long Date</p>
@Html.PropertyFor(m => m.CurrentPage.CreationDate, new { Tag = "LongDate" })
<p>Short Date</p>
@Html.PropertyFor(m => m.CurrentPage.CreationDate, new { Tag = "ShortDate" })
<p>Posted Date with Time</p>
@Html.PropertyFor(m => m.CurrentPage.CreationDate, new { Tag = "PostedDateWithTime" })
[/pre]
You can take this idea a step further and make your DateTime format configurable with a property on the block or page by using the value of the property as the Tag value. While you can simply use a string property for this purpose, I find it better to define enums for situations like this.
[pre class="brush:csharp" title="DateFormat Enum & Page Definition"]
namespace Samples.Global
{
    public class Definitions
    {
        public enum DateFormat
        {
            DateTime,
            LongDate,
            ShortDate,
            PostedDateWithTime
        }
    }
}

namespace Tradeweb.Models.Pages
{
    [ContentType(DisplayName = "Common Page", GUID = "27ddcba8-b652-409e-b771-6dd81fa8ab78", Description = "")]
    public class CommonPage : BasePageData
    {
        public virtual DateTime CreationDate { get; set; }
        [Enum(typeof(Samples.Global.Definitions.DateFormat))]
        public virtual Samples.Global.Definitions.DateFormat DateFormat { get; set; }
    }
}
[/pre] [pre class="brush:razor" title="Razor Syntax"]
<p>Selectable format</p>
@Html.PropertyFor(m=> m.CurrentPage.CreationDate, new { Tag = Model.CurrentPage.DateFormat.ToString() })
[/pre]

Conclusion

As I mentioned, while this article focuses heavily on the DateTime property in these examples, many of these approaches can be utilized for other properties throughout your site, and becoming familiar with them will allow you to improve your users' editing experience.

No comments:

Post a Comment