MindFusion.Scheduling for ASP.NET Programmer's Guide
Custom Items and Resources

MindFusion.Scheduling for ASP.NET gives you the possibility of extending the available Item and Resource classes to include your own business logic in a Schedule.

Custom Items

In order to integrate your custom class into the calendar API, you must do the following:

1. Derive from either Item or Appointment and provide a parameterless constructor to your custom class and extend it to your liking.

For example:

C#  Copy Code

using MindFusion.Scheduling;

public class MyCustomItem: Appointment
{
    public MyCustomItem()
    {
    }

    public int RecordedValue
    {
        get { return recordedValue; }
        set { recordedValue = value; }
    }

    private int recordedValue;
}

Visual Basic  Copy Code

Imports MindFusion.Scheduling

Public Class MyCustomItem
    Inherits Appointment

    Public Sub New()
    End Sub

    Public Property RecordedValue() As Integer
        Get
            Return recValue
        End Get
        Set(ByVal value As Integer)
            recValue = value
        End Set
    End Property

    Private recValue As Integer
End Class

2. Enable ViewState on your custom item.

In order to save your custom properties in the ViewState of your custom item you should override the SaveViewState and LoadViewState methods of the Item class.

C#  Copy Code

public override object SaveViewState()
{
    ViewState["_recordedValue"] = RecordedValue;
    return base.SaveViewState();
}

public override void LoadViewState(object state)
{
    base.LoadViewState(state);
    if (ViewState["_recordedValue"] != null)
    {
        RecordedValue = (int)ViewState["_recordedValue"];
    }
}

Visual Basic  Copy Code

Public Overrides Function SaveViewState() As Object
    ViewState("_recordedValue") = RecordedValue
    Return MyBase.SaveViewState()
End Function

Public Overrides Sub LoadViewState(state As Object)
    MyBase.LoadViewState(state)
    If ViewState("_recordedValue") IsNot Nothing Then
        RecordedValue = CInt(ViewState("_recordedValue"))
    End If
End Sub

3. Serialization

Serialization of custom items requires additional efforts. Attempts to save a schedule containing items of type MyCustomItem will result in an InvalidOperationException exception. In order to support serialization, we have to register MyCustomItem with the schedule. For this purpose, add the following line of code somewhere in the page's Load event handler:

C#  Copy Code

Schedule.RegisterItemClass(typeof(MyCustomItem), "myitem", 1);

Visual Basic  Copy Code

Schedule.RegisterItemClass(GetType(MyCustomItem), "myitem", 1)

Now the exception won't be thrown, but we still need to override the SaveTo and LoadFrom methods of the Appointment class in order to serialize the custom property RecordedValue. The code below illustrates how this is done for the SaveTo and LoadFrom overloads that serialize item's contents in binary streams.

C#  Copy Code

public override void SaveTo(System.IO.BinaryWriter writer, BinarySerializationContext context)
{
    base.SaveTo(writer, context);
    context.WriteInt(writer, recordedValue);
}

public override void LoadFrom(System.IO.BinaryReader reader, BinarySerializationContext context)
{
    base.LoadFrom(reader, context);
    recordedValue = context.ReadInt(reader);
}

Visual Basic  Copy Code

Public Overloads Overrides Sub SaveTo(ByVal writer As System.IO.BinaryWriter, ByVal context As BinarySerializationContext)
    MyBase.SaveTo(writer, context)
    context.WriteInt(writer, recValue)
End Sub

Public Overloads Overrides Sub LoadFrom(ByVal reader As System.IO.BinaryReader, ByVal context As SerializationContext)
    MyBase.LoadFrom(reader, context)
    recValue = context.ReadInt(reader)
End Sub

A code that implements serialization of items in XML format would look similar to the above, but it will override the other SaveTo and LoadFrom method overloads.

4. Recurrences

Recurrence of items from a custom class is not handled wholly automatically. Let's take a closer look at how recurrence works in MindFusion.Scheduling for ASP.NET. When a recurrence pattern is associated with an existing item, the item is said to become the master of the recurrence. Every time the occurrences of a master item must be displayed in a given time range, they are generated by instantiating new objects of the same type as that of the master. This is necessary in order to support infinite recurrences, where the generation of all recurrence instances is not possible. When the occurrences are generated, their properties are copied from the master item. This is done automatically for the built-in item properties, but not for those added by a derived item class. In order to copy the custom properties from the master item to a particular occurrence, you need to override the CopyOccurrence method. The following code sample shows how to do this.

C#  Copy Code

protected override void CopyOccurrence(Item master)
{
    base.CopyOccurrence(master);
    recordedValue = (master as MyCustomItem).RecordedValue;
}

Visual Basic  Copy Code

Protected Overloads Overrides Sub CopyOccurrence(ByVal master As Item)
    MyBase.CopyOccurrence(master)
    recValue = CType(master, MyCustomItem).RecordedValue
End Sub

In addition, it is recommended to mark an item from a recurrent series as an exception when the value of a custom property changes. Otherwise, the change will be lost the next time the instance is generated.

C#  Copy Code

// ...
public int RecordedValue
{
    get { return recordedValue; }
    set
    {
        recordedValue = value;

        if (Recurrence != null)
            Recurrence.MarkException(this, false);
    }
}
// ...

Visual Basic  Copy Code

' ...
Public Property RecordedValue() As Integer
    Get
        Return recordedValue
    End Get
    Set(ByVal value As Integer)
        recValue = value

        If Not Recurrence Is Nothing Then
            Recurrence.MarkException(Me, False)
        End If
    End Set
End Property
' ...

5. Accessing your custom properties from the client

In order to transfer the custom properties of your items to the client side, you must supply a JavaScriptConverter that will handle the serialization to a JSON string, that will be used by the Calendar to recreate the items, created on the server. The easiest way to do that is to derive from ItemConverter, override the SupportedTypes property and include the serialization logic in the Serialize override. For example:

C#  Copy Code

using MindFusion.Scheduling.WebForms;

class MyCustomItemConverter : ItemConverter
{
    public MyCustomItemConverter(Calendar calendar) : base(calendar)
    {
    }

    public override List<Type> SupportedTypes
    {
        get
        {
            return new List<Type>() { typeof(MyCustomItem) };
        }
    }

    public override IDictionary<string, object> Serialize(object obj, System.Web.Script.Serialization.JavaScriptSerializer serializer)
    {
        MyCustomItem item = obj as MyCustomItem;
        if (item != null)
        {
            IDictionary<string, object> json = base.Serialize(obj, serializer);
            if (json != null)
            {
                json.Add("recordedValue", item.RecordedValue);
                return json;
            }
        }
        return null;
    }
}

Visual Basic  Copy Code

Imports MindFusion.Scheduling.WebForms

Class MyCustomItemConverter
    Inherits ItemConverter
 
    Public Sub New(calendar As Calendar)
        MyBase.New(calendar)
    End Sub

    Public Overrides ReadOnly Property SupportedTypes() As List(Of Type)
        Get
            Return New List(Of Type)(New Type() {GetType(MyCustomItem)})
        End Get
    End Property

    Public Overrides Function Serialize(obj As Object, serializer As System.Web.Script.Serialization.JavaScriptSerializer) As IDictionary(Of String, Object)
        Dim item As MyCustomItem = TryCast(obj, MyCustomItem)
        If item IsNot Nothing Then
            Dim json As IDictionary(Of String, Object) = MyBase.Serialize(obj, serializer)
            If json IsNot Nothing Then
                json.Add("recordedValue", item.RecordedValue)
                Return json
            End If
        End If
        Return Nothing
    End Function
End Class

After you have created your custom JavaScriptConverter, you must register it for serialization with the Schedule, by handling the ItemSerialization event of the Calendar class:

C#  Copy Code

// ...

Calendar1.ItemSerialization += new EventHandler<SerializationEventArgs>(Calendar1_ItemSerialization);

// ...

protected void Calendar1_ItemSerialization(object sender, SerializationEventArgs e)
{
    e.Serializer.RegisterConverters(new JavaScriptConverter[] { new MyCustomItemConverter(Calendar1) });
}

Visual Basic  Copy Code

' ...

AddHandler Calendar1.ItemSerialization, AddressOf Calendar1_ItemSerialization

' ...

Protected Sub Calendar1_ItemSerialization(sender As Object, e As SerializationEventArgs)
    e.Serializer.RegisterConverters(New JavaScriptConverter() {New MyCustomItemConverter(Calendar1)})
End Sub

Now, all you need to do is crate a new instance of your custom item and add it to the Schedule.Items collection.

C#  Copy Code

MyCustomItem myItem = new MyCustomItem();
myItem.StartTime = DateTime.Today;
myItem.EndTime = myItem.StartTime.AddHours(10);
myItem.HeaderText = "My Custom Item!";
myItem.RecordedValue = 42;

Calendar1.Schedule.Items.Add(myItem);

Visual Basic  Copy Code

Dim myItem As New MyCustomItem()
myItem.StartTime = DateTime.Today
myItem.EndTime = myItem.StartTime.AddHours(10)
myItem.HeaderText = "My Custom Item!"
myItem.RecordedValue = 42

Calendar1.Schedule.Items.Add(myItem)

Then you can read the value stored in RecordedValue:

JavaScript  Copy Code

function showRecordedValue() {
    var calendar = $find('Calendar1');

    var myCustomItem = calendar.getItems()[0];
   
    alert(['This is my custom value: ', myCustomItem.recordedValue]);
}

Resources

The same pattern can be followed in order to use custom resource classes with MindFusion.Scheduling for ASP.NET.

The following is a full example on how to create a custom resource class:

ASPX  Copy Code

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="CustomResourceSample._Default" %>
<%@ Register Assembly="MindFusion.Scheduling.WebForms" Namespace="MindFusion.Scheduling.WebForms"
    TagPrefix="MindFusion" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Custom Resources</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div>
        <MindFusion:Calendar ID="Calendar1" runat="server" Height="600px" Width="800px" 
            OnResourceSerialization="Calendar1_ResourceSerialization">
        </MindFusion:Calendar>
    </div>
    </form>
</body>

C#  Copy Code

using System;
using System.Collections.Generic;
using System.Web.Script.Serialization;
using MindFusion.Scheduling;
using MindFusion.Scheduling.WebForms;

namespace CustomResourceSample
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (Schedule.IsClassRegistered("customres"))
                Schedule.UnregisterClass("customres");
            Schedule.RegisterResourceClass(typeof(MyCustomResource), "customres", 1);

            if (!IsPostBack)
            {
                MyCustomResource myResource = new MyCustomResource();
                myResource.Name = "myresource";
                myResource.ResourceDescription = "Short description of my resource.";

                Calendar1.Schedule.Resources.Add(myResource);

                Appointment myResourceHolder = new Appointment();
                myResourceHolder.StartTime = DateTime.Now;
                myResourceHolder.EndTime = myResourceHolder.StartTime.AddHours(2);
                myResourceHolder.HeaderText = "Item with custom resource";
                myResourceHolder.Resources.Add(myResource);

                Calendar1.Schedule.Items.Add(myResourceHolder);
            }
        }

        protected void Calendar1_ResourceSerialization(object sender, SerializationEventArgs e)
        {
            e.Serializer.RegisterConverters(new JavaScriptConverter[] { new MyCustomResourceConverter(Calendar1) });
        }
    }

    class MyCustomResource : Resource
    {
        public MyCustomResource()
          : base()
        { }

        public string ResourceDescription { get; set; }

        public override object SaveViewState()
        {
            if (!String.IsNullOrEmpty(ResourceDescription))
                ViewState["_resourceDescription"] = ResourceDescription;
            return base.SaveViewState();
        }

        public override void LoadViewState(object state)
        {
            base.LoadViewState(state);
            if (ViewState["_resourceDescription"] != null)
                ResourceDescription = ViewState["_resourceDescription"].ToString();
        }

        public override void SaveTo(System.Xml.XmlElement element, XmlSerializationContext context)
        {
            base.SaveTo(element, context);
            context.WriteString(ResourceDescription, "ResourceDescription", element);
        }

        public override void LoadFrom(System.Xml.XmlElement element, XmlSerializationContext context)
        {
            base.LoadFrom(element, context);
            ResourceDescription = context.ReadString("ResourceDescription", element);
        }
    }

    class MyCustomResourceConverter : ResourceConverter
    {
        public MyCustomResourceConverter(Calendar calendar)
          : base(calendar)
        { }

        public override IEnumerable<Type> SupportedTypes
        {
            get
            {
                return new List<Type>() { typeof(MyCustomResource) };
            }
        }

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
        {
            MyCustomResource resource = obj as MyCustomResource;
            if (resource != null)
            {
                IDictionary<string, object> json = base.Serialize(obj, serializer);
                if (json != null)
                {
                    if (!String.IsNullOrEmpty(resource.ResourceDescription))
                        json.Add("resourceDescription", resource.ResourceDescription);
                    return json;
                }
            }
            return null;
        }
    }
}

Visual Basic  Copy Code

Imports System.Collections.Generic
Imports System.Web.Script.Serialization
Imports MindFusion.Scheduling
Imports MindFusion.Scheduling.WebForms

Public Partial Class _Default
  Inherits System.Web.UI.Page

    Protected Sub Page_Load(sender As Object, e As EventArgs)
        If Schedule.IsClassRegistered("customres") Then
            Schedule.UnregisterClass("customres")
        End If
        Schedule.RegisterResourceClass(GetType(MyCustomResource), "customres", 1)

        If Not IsPostBack Then
            Dim myResource As New MyCustomResource()
            myResource.Name = "myresource"
            myResource.ResourceDescription = "Short description of my resource."

            Calendar1.Schedule.Resources.Add(myResource)

            Dim myResourceHolder As New Appointment()
            myResourceHolder.StartTime = DateTime.Now
            myResourceHolder.EndTime = myResourceHolder.StartTime.AddHours(2)
            myResourceHolder.HeaderText = "Item with custom resource"
            myResourceHolder.Resources.Add(myResource)

            Calendar1.Schedule.Items.Add(myResourceHolder)
        End If
    End Sub

    Protected Sub Calendar1_ResourceSerialization(sender As Object, e As SerializationEventArgs)
        e.Serializer.RegisterConverters(New JavaScriptConverter() {New MyCustomResourceConverter(Calendar1)})
    End Sub
End Class

Class MyCustomResource
  Inherits Resource

    Public Sub New()
        MyBase.New()
    End Sub

    Public Property ResourceDescription() As String
        Get
            Return description
        End Get
        Set(ByVal value As String)
            description = Value
        End Set
    End Property
    Private description As String

    Public Overrides Function SaveViewState() As Object
        If Not [String].IsNullOrEmpty(ResourceDescription) Then
            ViewState("_resourceDescription") = ResourceDescription
        End If
        Return MyBase.SaveViewState()
    End Function

    Public Overrides Sub LoadViewState(state As Object)
        MyBase.LoadViewState(state)

        If ViewState("_resourceDescription") IsNot Nothing Then
            ResourceDescription = ViewState("_resourceDescription").ToString()
        End If
    End Sub

    Public Overrides Sub SaveTo(element As System.Xml.XmlElement, context As XmlSerializationContext)
        MyBase.SaveTo(element, context)
        context.WriteString(ResourceDescription, "ResourceDescription", element)
    End Sub

    Public Overrides Sub LoadFrom(element As System.Xml.XmlElement, context As XmlSerializationContext)
        MyBase.LoadFrom(element, context)
        ResourceDescription = context.ReadString("ResourceDescription", element)
    End Sub
End Class

Class MyCustomResourceConverter
  Inherits ResourceConverter
    Public Sub New(calendar As Calendar)
        MyBase.New(calendar)
    End Sub

    Public Overrides ReadOnly Property SupportedTypes() As IEnumerable(Of Type)
        Get
            Return New List(Of Type)(New Type() {GetType(MyCustomResource)})
        End Get
    End Property

    Public Overrides Function Serialize(obj As Object, serializer As JavaScriptSerializer) As IDictionary(Of String, Object)
        Dim resource As MyCustomResource = TryCast(obj, MyCustomResource)
        If resource IsNot Nothing Then
            Dim json As IDictionary(Of String, Object) = MyBase.Serialize(obj, serializer)
            If json IsNot Nothing Then
                If Not [String].IsNullOrEmpty(resource.ResourceDescription) Then
                    json.Add("resourceDescription", resource.ResourceDescription)
                End If
                Return json
            End If
        End If
        Return Nothing
    End Function
End Class