Monthly Archives: January 2015

How to use EditFrame in Sitecore MVC

EditFrameDatetime

I’ve been working in Sitecore MVC for some months now and it’s great. But I do miss some nice controls like the sc:EditFrame. But fear not there is a solution, Glass has the answer 🙂

Thank God for Glass – I looked at how they did it and copied the code from GlassEditFrame.

public class EditFrameRendering : IDisposable
{
    private readonly EditFrame _editFrame;
    private readonly HtmlTextWriter _htmlWriter;

    public EditFrameRendering(TextWriter writer, string dataSource, string buttons)
    {
        this._htmlWriter = new HtmlTextWriter(writer);
        this._editFrame = new EditFrame { DataSource = dataSource, Buttons = buttons };
        this._editFrame.RenderFirstPart(this._htmlWriter);
    }


    public void Dispose()
    {
        this._editFrame.RenderLastPart(this._htmlWriter);
        this._htmlWriter.Dispose();
    }
}

Next thing to do is to make a helper class for the view, again I looked at Glass and copied from HtmlHelperExtensions .

public static class HtmlExtensions
{
    /// <summary>
    /// Method for edit frame
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="helper"></param>
    /// <param name="dataSource"></param>
    /// <param name="buttons"></param>
    /// <returns></returns>
    public static EditFrameRendering BeginEditFrame<T>(this HtmlHelper<T> helper, string dataSource, string buttons)
    {
        EditFrameRendering frame = new EditFrameRendering(helper.ViewContext.Writer, dataSource, buttons);
        return frame;
    }

}

Add your buttons to Core under Edit Frame Buttons – /sitecore/content/Applications/WebEdit/Edit Frame Buttons/
EditFrameButtons

Finally how to use the EditFrame in the view, here I’m using the “DateTime with TimeZone” control from previous post – Create a custom control in Sitecore: DateTime with TimeZone

@using (Html.BeginEditFrame(Html.Sitecore().CurrentRendering.DataSource, "/sitecore/content/Applications/WebEdit/Edit Frame Buttons/CustomButtons/Fields/DateTimeWithTimeZone"))
{
    if (Html.IsEditMode())
    {
     <a href="#">Edit countdown date: @Html.Sitecore().Field("CountdownDate", new { DisableWebEdit = true })</a>
    }

}

That’s all for now folks 🙂

Advertisements

Create a custom control in Sitecore: DateTime with TimeZone

DateTimeWithTimeZone

2015 will be a great year, especially when Sitecore recently released their flagship Sitecore 8. I just love the design and all the new cool things that comes with it.

I want to show you guys how to make a custom control – DateTime with TimeZone. There is a great post out there which explains it very well – Sitecore Date Time picker with time zone. In this case I needed to store the DateTime together with the TimeZone.
In “raw values” the control will look like this:
DateTimeWithTimeZoneRaw

What we need to do:
1. Create the control(Content editor)
2. Register the control in Sitecore(Core database)
3. Create a render field pipeline(For page editor and the “website”)

Let us dive in to the code 🙂

Create the control

First we do the actual control which will have a DateTime picker and a dropdown containing all TimeZones. We need to create a class which inherits from Sitecore.Shell.Applications.ContentEditor.DateTime(Sitecore DateTime picker). In the DoRender method we will add/create the dropdown with timezones.

protected override void DoRender(System.Web.UI.HtmlTextWriter output)
{

    if (!string.IsNullOrWhiteSpace(base.RealValue) && base.RealValue.Contains("|"))
    {
        TimeZone = _dateWithTimeZoneService.GetTimeZoneValue(base.RealValue);
        base.SetValue(_dateWithTimeZoneService.CalculateDateTimeWithTimeZone(base.RealValue));
    }
                 
    output.Write("<div style='display:inline;float:left'>");
    base.DoRender(output);
    output.Write(TimeZoneDroplist());
    output.Write("</div>");

}

Since we store the DateTime value together with the TimeZone id (base.RealValue property from class Sitecore.Shell.Applications.ContentEditor.Date holds the “raw value”) we need to trick the Sitecore DateTime Picker (It expects a DateTime value). That’s why we set a calculated DateTime value in the base.SetValue method (From Sitecore.Shell.Applications.ContentEditor.Date)

The method for generating the dropdown with timezones is quite straight forward. To get the timezones we use the System.TimeZoneInfo.GetSystemTimeZones() method.

private string TimeZoneDroplist()
{
    StringBuilder stringBuilderSelect = new StringBuilder();

    stringBuilderSelect.AppendFormat(@"<select {0} {1} >", this.GetControlAttributes(), string.IsNullOrWhiteSpace(base.RealValue) ? "disabled" : string.Empty);
    stringBuilderSelect.AppendLine();

    stringBuilderSelect.AppendFormat(@"<option value='' >{0}</option>", "Please select a time zone");
           
    foreach (TimeZoneInfo timeZoneInfo in TimeZoneInfo.GetSystemTimeZones())
    {
        stringBuilderSelect.AppendFormat(@"<option value='{0}' {1} >{2}</option>", timeZoneInfo.Id, TimeZone == timeZoneInfo.Id ? "selected" : string.Empty, timeZoneInfo.DisplayName);
        stringBuilderSelect.AppendLine();
    }

    stringBuilderSelect.AppendLine();
    stringBuilderSelect.Append("</select>");

    return stringBuilderSelect.ToString();
}

The TimeZone property(viewstate) holds the selected TimeZone Id.

To store the the combined data, DateTime value and TimeZone id, we need to set the RealValue property (From Sitecore.Shell.Applications.ContentEditor.Date). We will do this in the LoadPostData method.

protected override bool LoadPostData(string value)
{
    if (value == null)
        return false;

    if (!base.RealValue.Contains(value))
        base.RealValue = string.Format("{0}|{1}", _dateWithTimeZoneService.GetDateTimeValue(base.RealValue), value);
           
    return true;
}

The “value parameter” will contain the selected TimeZone id from the TimeZone dropdown.

Here is the full code for the control.

public class DateTimePickerWithTimeZone : Sitecore.Shell.Applications.ContentEditor.DateTime
{

    private readonly IDateWithTimeZoneService _dateWithTimeZoneService;

    public string Format
    {
        get { return base.GetViewStateString("Value.Format"); }
        set
        {
            base.SetViewStateString("Value.Format", value);
        }
    }

    public string TimeZone
    {
        get { return base.GetViewStateString("Value.TimeZone"); }
        set
        {
            base.SetViewStateString("Value.TimeZone", value);
        }
    }

    public DateTimePickerWithTimeZone(): base()
    {
        _dateWithTimeZoneService = new DateWithTimeZoneService();
        Format = "MM/dd/yyyy";
    }

    protected override void DoRender(System.Web.UI.HtmlTextWriter output)
    {

        if (!string.IsNullOrWhiteSpace(RealValue) && base.RealValue.Contains("|"))
        {
            TimeZone = _dateWithTimeZoneService.GetTimeZoneValue(RealValue);
            SetValue(_dateWithTimeZoneService.CalculateDateTimeWithTimeZone(RealValue));
        }
                 
        output.Write("<div style='display:inline;float:left'>");
        base.DoRender(output);
        output.Write(TimeZoneDroplist());
        output.Write("</div>");

    }

    private string TimeZoneDroplist()
    {
        StringBuilder stringBuilderSelect = new StringBuilder();

        stringBuilderSelect.AppendFormat(@"<select {0} {1} >", this.GetControlAttributes(), string.IsNullOrWhiteSpace(RealValue) ? "disabled" : string.Empty);
        stringBuilderSelect.AppendLine();

        stringBuilderSelect.AppendFormat(@"<option value='' >{0}</option>", "Please select a time zone");
           
        foreach (TimeZoneInfo timeZoneInfo in TimeZoneInfo.GetSystemTimeZones())
        {
            stringBuilderSelect.AppendFormat(@"<option value='{0}' {1} >{2}</option>", timeZoneInfo.Id, TimeZone == timeZoneInfo.Id ? "selected" : string.Empty, timeZoneInfo.DisplayName);
            stringBuilderSelect.AppendLine();
        }

        stringBuilderSelect.AppendLine();
        stringBuilderSelect.Append("</select>");

        return stringBuilderSelect.ToString();
    }

    protected override bool LoadPostData(string value)
    {
        if (value == null)
            return false;

        if (!base.RealValue.Contains(value))
            base.RealValue = string.Format("{0}|{1}", _dateWithTimeZoneService.GetDateTimeValue(base.RealValue), value);
           
        return true;
    }
       
}

For calculating DateTime with Timezone and refining data we need a service/helper class(It will also be used by the render field class)

public interface IDateWithTimeZoneService
{
    string CalculateDateTimeWithTimeZone(string combinedDateTimeAndTimeZoneValueSeperatedWithPipe);

    string GetDateTimeValue(string combinedDateTimeAndTimeZoneValueSeperatedWithPipe);

    string GetTimeZoneValue(string combinedDateTimeAndTimeZoneValueSeperatedWithPipe);
}

public class DateWithTimeZoneService : IDateWithTimeZoneService
{

    /// <summary>
    /// Method for calculating datetime with timezone
    /// </summary>
    /// <param name="combinedDateTimeAndTimeZoneValueSeperatedWithPipe"></param>
    /// <returns></returns>
    public string CalculateDateTimeWithTimeZone(string combinedDateTimeAndTimeZoneValueSeperatedWithPipe)
    {

        String dateTime = GetDateTimeValue(combinedDateTimeAndTimeZoneValueSeperatedWithPipe);
        string zoneId = GetTimeZoneValue(combinedDateTimeAndTimeZoneValueSeperatedWithPipe);

        Assert.IsTrue(DateUtil.IsIsoDate(dateTime), "Not valid date");

        Assert.IsNotNull(zoneId, "TimeZone id is missing");

        DateTime currentDateTimeUtc = DateUtil.IsoDateToDateTime(dateTime).ToUniversalTime();
        TimeZoneInfo zone = TimeZoneInfo.FindSystemTimeZoneById(zoneId);
        DateTime localDateTime = TimeZoneInfo.ConvertTimeFromUtc(currentDateTimeUtc, zone);

        return DateUtil.ToIsoDate(localDateTime);
    }

    /// <summary>
    /// Method to get datetime
    /// </summary>
    /// <param name="combinedDateTimeAndTimeZoneValueSeperatedWithPipe"></param>
    /// <returns></returns>
    public string GetDateTimeValue(string combinedDateTimeAndTimeZoneValueSeperatedWithPipe)
    {
        return combinedDateTimeAndTimeZoneValueSeperatedWithPipe.Contains("|") ? combinedDateTimeAndTimeZoneValueSeperatedWithPipe.Substring(0, combinedDateTimeAndTimeZoneValueSeperatedWithPipe.IndexOf("|", System.StringComparison.Ordinal)) : combinedDateTimeAndTimeZoneValueSeperatedWithPipe;
    }

    /// <summary>
    /// Method to get timezone id
    /// </summary>
    /// <param name="combinedDateTimeAndTimeZoneValueSeperatedWithPipe"></param>
    /// <returns></returns>
    public string GetTimeZoneValue(string combinedDateTimeAndTimeZoneValueSeperatedWithPipe)
    {
        return combinedDateTimeAndTimeZoneValueSeperatedWithPipe.Contains("|") ? combinedDateTimeAndTimeZoneValueSeperatedWithPipe.Substring(combinedDateTimeAndTimeZoneValueSeperatedWithPipe.IndexOf("|", System.StringComparison.Ordinal) + 1) : string.Empty;
    }
}

Register the control in Sitecore

Next thing to do is to register the control in Sitecore. We will do this in System/Field types in the Core database. The easiest way is to copy the DateTime item in System Types – /sitecore/system/Field types/Simple Types/Datetime. Give it a good name and remove the “WebEdit Buttons” folder.
registercontrol
Enter the assembly name and the class for the control.

To use it just create a template and select our new field type.
SitecoreTemplate

Create a render field pipeline

OK so now we have a nice control which will work perfect in the content editor but not in the page editor or on the actual “website”. Since we store both the DateTime value and the Timezone id in a field it will mess up the render field pipeline. To fix it we need to make our own “render field pipeline” and it will be very similar to Sitecore’s Sitecore.Pipelines.RenderField.GetDateFieldValue. So we just duplicate the class and give it a good name – GetDateTimeWithTimeZoneValue

public class GetDateTimeWithTimeZoneValue
{

    private readonly IDateWithTimeZoneService _dateWithTimeZoneService;

    public GetDateTimeWithTimeZoneValue()
    {
        _dateWithTimeZoneService = new DateWithTimeZoneService();
    }

    /// <summary>
    /// Runs the processor.
    /// 
    /// </summary>
    /// <param name="args">The arguments.</param>
    public void Process(RenderFieldArgs args)
    {
        string fieldTypeKey = args.FieldTypeKey;
            
        if (fieldTypeKey != "datetimewithtimezone")
            return;

        DateRenderer renderer = this.CreateRenderer();
        renderer.Item = args.Item;
        renderer.FieldName = args.FieldName;
        renderer.FieldValue = _dateWithTimeZoneService.CalculateDateTimeWithTimeZone(args.FieldValue);

        renderer.Parameters = args.Parameters;
            
        if (!string.IsNullOrEmpty(args.Parameters["format"]))
            args.WebEditParameters["format"] = args.Parameters["format"];
            
        RenderFieldResult renderFieldResult = renderer.Render();
        args.Result.FirstPart = renderFieldResult.FirstPart;
        args.Result.LastPart = renderFieldResult.LastPart;
    }

    /// <summary>
    /// Creates the renderer.
    /// 
    /// </summary>
    /// 
    /// <returns>
    /// The renderer.
    /// </returns>
    protected virtual DateRenderer CreateRenderer()
    {
        return new DateRenderer();
    }

}

We will set the renderer.FieldValue by calculating the correct DateTime by using the combined value(DateTime and TimeZoneId) in args.FieldValue.

In order to make the pipeline work we need to do a patch config file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <renderField>
        <processor type="Sandbox.CustomFields.Pipelines.RenderField.GetDateTimeWithTimeZoneValue, Sandbox.CustomFields"
                    patch:after="processor[@type='Sitecore.Pipelines.RenderField.GetDateFieldValue, Sitecore.Kernel']">
        </processor>
      </renderField>
    </pipelines>
  </sitecore>
</configuration>

That’s all for now folks 🙂

Happy New Year!