The client-side dhtmlxScheduler that lies in the core of dhtmlxScheduler.NET accepts data in a plain JSON format which is either loaded via ajax or put into the scheduler from object data source.
While these methods can be used as they are, Scheduler.NET provides wrappers that allow implementing CRUD without worrying about the format of the loaded data.
Let's start with the basic conventions you'll see in the most of our samples.
The code of the Controller for ASP.NET MVC may look as follows:
public class CalendarController : Controller
{
public ActionResult Index()
{
var scheduler = new DHXScheduler(this);
// ajax call for Data on loading
scheduler.LoadData = true;
// send changes to the Save action
scheduler.EnableDataprocessor = true;
return View(scheduler);
}
public ContentResult Data()
{
var data = new SchedulerAjaxData(
new List<CalendarEvent>{
new CalendarEvent{
id = 1,
text = "Sample Event",
start_date = new DateTime(2017, 09, 03, 6, 00, 00),
end_date = new DateTime(2017, 09, 03, 8, 00, 00)
},
new CalendarEvent{
id = 2,
text = "New Event",
start_date = new DateTime(2017, 09, 05, 9, 00, 00),
end_date = new DateTime(2017, 09, 05, 12, 00, 00)
},
new CalendarEvent{
id = 3,
text = "Multi-day Event",
start_date = new DateTime(2017, 09, 03, 10, 00, 00),
end_date = new DateTime(2017, 09, 10, 12, 00, 00)
}
}
);
return (ContentResult)data;
}
public ContentResult Save(int? id, FormCollection actionValues)
{
var action = new DataAction(actionValues);
try
{
var changedEvent = DHXEventsHelper.Bind<CalendarEvent>(actionValues);
switch (action.Type)
{
case DataActionTypes.Insert:
//do insert
//action.TargetId = changedEvent.id; // assign post operational id
break;
case DataActionTypes.Delete:
//do delete
break;
default:// "update"
//do update
break;
}
}
catch
{
action.Type = DataActionTypes.Error;
}
return (ContentResult)new AjaxSaveResponse(action);
}
}
Let's consider Index and Data actions. The Save action is explained here.
As you can see, inside the Index action we create scheduler and tell it to load data:
var scheduler = new DHXScheduler(this);
scheduler.LoadData = true;
And the Data action serves as a data source.
What do we need to know now:
Specifying a different action:
var scheduler = new DHXScheduler(this);
scheduler.DataAction = "Events";
scheduler.LoadData = true;
Specifying a complete URL explicitly:
var scheduler = new DHXScheduler(); // !! use default constructor
scheduler.DataAction = Url.Content("~/data.ashx");
scheduler.LoadData = true;
At the code level scheduler accepts JSON data which can be generated quite easily from objects.
However, due to special requirements to the naming of model properties and compatible Date-Time formats, we encourage you to use the DHTMLX.Scheduler.Data.SchedulerAjaxData class which provides JSON serializer for IEnumerable collections. The examples of usage are given below.
public ContentResult Data()
{
var data = new SchedulerAjaxData(
new List<CalendarEvent>{
new CalendarEvent{
id = 1,
text = "Sample Event",
start_date = new DateTime(2012, 09, 03, 6, 00, 00),
end_date = new DateTime(2012, 09, 03, 8, 00, 00)
},
new CalendarEvent{
id = 2,
text = "New Event",
start_date = new DateTime(2012, 09, 05, 9, 00, 00),
end_date = new DateTime(2012, 09, 05, 12, 00, 00)
},
new CalendarEvent{
id = 3,
text = "Multi-day Event",
start_date = new DateTime(2012, 09, 03, 10, 00, 00),
end_date = new DateTime(2012, 09, 10, 12, 00, 00)
}
}
);
return (ContentResult)data;
}
<%@ WebHandler Language="C#" CodeBehind="Data.ashx.cs" Class="SchedulerNetAsp.Data" %>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using DHTMLX.Scheduler.Data;
namespace SchedulerNetAsp
{
public class Data : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
var data = new SchedulerAjaxData(
new List<CalendarEvent>{
new CalendarEvent{
id = 1,
text = "Sample Event",
start_date = new DateTime(2012, 09, 03, 6, 00, 00),
end_date = new DateTime(2012, 09, 03, 8, 00, 00)
},
new CalendarEvent{
id = 2,
text = "New Event",
start_date = new DateTime(2012, 09, 05, 9, 00, 00),
end_date = new DateTime(2012, 09, 05, 12, 00, 00)
},
new CalendarEvent{
id = 3,
text = "Multi-day Event",
start_date = new DateTime(2012, 09, 03, 10, 00, 00),
end_date = new DateTime(2012, 09, 10, 12, 00, 00)
}
}
);
context.Response.ContentType = "text/json";
context.Response.Write(data.ToString());
}
public bool IsReusable { get { return false; } }
}
}
The SchedulerAjaxData helper accepts IEnumerable of generic objects as data collection, which means there are no predefined event classes you can use. Instead, you can provide any custom or anonymous classes and ensure they provide the following public properties:
In addition to these properties, items can contain any number of custom properties. Once loaded into calendar, all of them will be available to the client-side API.
Note that items are converted to JSON, thus they shouldn't contain any circular references. SchedulerAjaxData also provides means for skipping circular references from JSON, please see the details below.
C# class definition
public class CalendarEvent
{
// id, text, start_date and end_date properties are mandatory
public int id { get; set; }
public string text { get; set; }
public DateTime start_date { get; set; }
public DateTime end_date { get; set; }
}
T-SQL table declaration
CREATE TABLE [CalendarEvents] (
[id] INT IDENTITY (1, 1) NOT NULL,
[text] TEXT NULL,
[start_date] DATETIME NOT NULL,
[end_date] DATETIME NOT NULL,
PRIMARY KEY (id)
);
The possibility to use Recurring Events requires extending data objects with additional properties. If you use recurring events, a minimal set of public properties will be the following
Again, any custom properties can be used.
C# class definition
public class RecurringEvent
{
public int id { get; set; }
public string text { get; set; }
public DateTime start_date { get; set; }
public DateTime end_date { get; set; }
public long? event_length { get; set; }
public int? event_pid { get; set; }
public string rec_type { get; set; }
}
T-SQL table declaration
CREATE TABLE [Events] (
[id] INT IDENTITY (1, 1) NOT NULL,
[text] TEXT NULL,
[start_date] DATETIME NOT NULL,
[end_date] DATETIME NOT NULL,
[event_length] BIGINT NULL,
[rec_type] NVARCHAR(50) NULL,
[event_pid] INT NULL,
PRIMARY KEY (id)
);
By default, Scheduler loads all data at once. It may become problematic when you use big event collections. In such situations you can use dynamic loading and load data by parts necessary to fill the viewable area of Scheduler.
To enable dynamic loading, you need to call the EnableDynamicLoading() method:
public ActionResult Index(){
...
var scheduler = new DHXScheduler(this);
scheduler.EnableDynamicLoading(SchedulerDataLoader.DynamicalLoadingMode.Day);
...
}
The enumeration SchedulerDataLoader.DynamicalLoadingMode has 4 possible values:
For example, if you set the 'month' mode, Scheduler will request data just for the current month and load remaining ones on demand.
Generated requests look like this:
Data?from=DATEHERE&to=DATEHERE
where DATEHERE is a valid date value of the yyyy-MM-dd format.
The received data can be parsed as follows:
var dateFrom = DateTime.ParseExact(this.Request.QueryString["from"], "yyyy-MM-dd", CultureInfo.InvariantCulture);
var dateTo = DateTime.ParseExact(this.Request.QueryString["to"], "yyyy-MM-dd", CultureInfo.InvariantCulture);
Static loading means that scheduler will be populated with the data during initialization and ajax loading won't be needed.
public ActionResult Index() {
var scheduler = new DHXScheduler(this);
var context = new DHXSchedulerDataContext();
scheduler.Data.Parse(context.Events.ToList()));
scheduler.EnableDataprocessor = true;
return View(scheduler);
}
Added items are stored in the inner object of the DHXSchedulerDataStore class called scheduler.Data.Pull.
You can get a certain item by its index using the line below:
var item = scheduler.Data.Pull[0];
To use a custom name for a mandatory data property, use the [DHXJson(Alias = 'mandatory_name')] serialization attribute.
For example, you have a class with the following properties:
using DHTMLX.Scheduler;
public class DataItem
{
public int EventId { get; set; }
public string Description { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
To load data as it is without changing the names of members (i.e. EventId → id, Description → text), alter the code as in:
using DHTMLX.Scheduler;
public class DataItem
{
[DHXJson(Alias = "id")]
public int EventId { get; set; }
[DHXJson(Alias = "text")]
public string Description { get; set; }
[DHXJson(Alias = "start_date")]
public DateTime Start { get; set; }
[DHXJson(Alias = "end_date")]
public DateTime End { get; set; }
}
While sending data properties to the client, you can exclude some of them from the list, if needed. To do this, use the [DHXJson(Ignore = 'true')] serialization attribute as in:
// the 'RelatedEvents' property won't be sent to the client
public class DataItem
{
public int id { get; set; }
public string text { get; set; }
public DateTime start_date { get; set; }
public DateTime end_date { get; set; }
[DHXJson(Ignore=true)]
public List<CalendarEvent> RelatedEvents { get; set; }
}
Note, you can't 'ignore' the mandatory properties: 'id', 'text', 'start_date', 'end_date'.
By default, the built-in serializer renders all public data properties. If you want to render a part of properties or your data objects don't fit the stated data requirements, you need to specify a custom render function.
How can you set a custom render function for data?
In most code samples we use the following technique for loading data:
public ContentResult Data()
{
var data = new SchedulerAjaxData((new DHXSchedulerDataContext()).Events);
return data;
}
But in reality, the code above is just a short variant of the following one:
public ContentResult Data()
{
var data = new SchedulerAjaxData((new DHXSchedulerDataContext()).Events);
return Content( data.Render() );
}
The SchedulerAjaxData.Render() method has 2 overloads:
1 . public string Render()
2 . public string Render(Action <System.Text.StringBuilder, object> renderer)
where:
The first overload is used during standard data loading. The second one allows specifying a custom render function (see a usage example below).
public ContentResult Data()
{
var data = new SchedulerAjaxData((new DHXSchedulerDataContext()).Events);
return Content( data.Render(eventRenderer) );
}
public void eventRenderer(System.Text.StringBuilder builder, object ev)
{
var item = ev as Event;
builder.Append(
string.Format(":{0}, text:\"{1}\", start_date:\"{2:MM/dd/yyyy HH:mm}\", end_date:\"{3:MM/dd/yyyy HH:mm}\"",
item.id,
HttpUtility.JavaScriptStringEncode(item.text),
item.start_date,
item.end_date)
);
}
While defining a custom function, please remember that you need to escape characters that may break JSON (such as quotes, newlines, etc.). For this purpose you can use the HttpUtility.JavaScriptStringEncode utility (.Net 4.0+), as it's shown in the above code. If you use an older version of .NET, you should provide some custom solution.
If you set a custom value for the DHXScheduler.Config.xml_date configuration option in the Index() action, don't forget to set the same date format for the SchedulerAjaxData object.
public ActionResult Index(){
...
scheduler.Config.xml_date = "%d/%m/%Y %H:%i";
...
}
public ContentResult Data(){
var events = (new DHXSchedulerDataContext()).Events;
var data = new SchedulerAjaxData(events);
data.DateFormat = "%d/%m/%Y %H:%i";
return (data);
}
See available variations of the format string here.
When you need to update data depending on the result of a server-side operation, you should do 2 things:
1 .Set the flag sched.Data.DataProcessor. UpdateFieldsAfterSave to true (in the Index() action).
2 .Set new values for properties in the response through the UpdateField(string fieldName, object fieldValue) method of the AjaxSaveResponse class (in the Save() action).
For example, you want to show events in different colors: events that are previous to the current date colored in gray, others - in blue.
So, you set the flag:
public ActionResult Index() {
var scheduler = new DHXScheduler(this);
...
scheduler.UpdateFieldsAfterSave();
...
return View(scheduler);
}
and assign the needed color:
public ContentResult Save(ColoredEvent changedEvent, FormCollection actionValues) {
var action = new DataAction(actionValues);
var color = "";
if (changedEvent.start_date < DateTime.Now)
color = "gray";
else
color = "blue";
...
var result = new AjaxSaveResponse(action);
result.UpdateField("textColor", color); // adds a new value
return result;
}
You can provide SchedulerAjaxData response with additional data that can later be accessed on the client side. Additional items are added as named collections to the SchedulerAjaxData.ServerList dictionary:
var data = new SchedulerAjaxData();
data.Add(new List<object>{
new { id = 1, text = "Event 1", start_date = new DateTime(2014, 12, 4, 10, 0, 0), end_date = new DateTime(2014, 12, 4, 12, 0, 0) },
new { id = 2, text = "Event 2", start_date = new DateTime(2014, 12, 4, 13, 15, 0), end_date = new DateTime(2014, 12, 4, 14, 0, 0) },
new { id = 3, text = "Event 3", start_date = new DateTime(2014, 12, 4, 13, 0, 0), end_date = new DateTime(2014, 12, 4, 13, 30, 0) },
});
data.ServerList.Add("dayoff", new List<object>{
new { Date = new DateTime(2014, 11, 1)},
new { Date = new DateTime(2014, 12, 14)}
});
return (ContentResult)data;
When such response is loaded to the page, the client-side component will fire the onOptionsLoad event, followed by the onXLE event.
The collection can be accessed on the client side using scheduler.serverList method (JavaScript):
var dayoff = scheduler.serverList("dayoff");
Note, for correct work, server list has to be initialized on the client side before loading. In order to do so, call scheduler.serverList("listName") before initialization of scheduler:
// C#
var scheduler = new DHXScheduler();
scheduler.BeforeInit.Add("initServerLists();");
// JS
function initServerLists(){
scheduler.serverList("dayoff");
}
Scheduler supports loading Timeline and Units options by default. It can be done by setting the ServerList property of Timeline or Units instance.
Initialization:
var scheduler = new DHXScheduler(this);
var timeline = new TimelineView("timeline", "section_id") {
RenderMode = TimelineView.RenderModes.Bar,
X_Unit = TimelineView.XScaleUnits.Day,
X_Size = 7,
X_Date = "%d"
};
scheduler.Views.Add(timeline);
timeline.ServerList = "sections";
Loading options:
var data = new SchedulerAjaxData(events);
data.ServerList.Add("sections", new List<object>{
new { key = "1", label = "Section 1" },
new { key = "2", label = "Section 2" },
new { key = "1", label = "Section 3" }
});
In this case no additional code is required. Timeline and Units views will fetch options from the lists with the specified names.
In the case of network errors or invalid server response, the client side will fire the "onLoadError" event, which can be captured with a JavaScript code:
scheduler.attachEvent("onLoadError", function(resp){
dhtmlx.message("The service is currently offline...");
});
See the event details here.
Use case: You need to refresh/reload calendar events each time user changes date in calendar.
As as solution, firstly you need to enable dynamic loading to load displayed date ranges on demand.
However, the built-in dynamic loading caches previously loaded values, so once you preload events for a certain date range, scheduler won't reload them from the server again.
It can be worked around by manually clearing scheduler on date change, by using client-side API.
// JS
scheduler.attachEvent("onBeforeViewChange", function (oldMode, oldDate, mode, date) {
if (oldMode != mode || +oldDate != +date)
scheduler.clearAll();
return true;
});
In case sections of the Timeline/Units view depend on the displayed date, they can be reloaded dynamically.
scheduler.attachEvent("onBeforeViewChange", function (oldMode, oldDate, mode, date) {
if (oldMode != mode || +oldDate != +date)
scheduler.clearAll();
return true;
});
Scheduler initialization
// C#
public ActionResult Index()
{
var scheduler = new DHXScheduler(this);
scheduler.Extensions.Add("../scheduler-config.js");// will define js settings here
var timeline = new TimelineView("timeline", "room_id");
scheduler.Views.Add(timeline);
timeline.ServerList = "timeline";
scheduler.EnableDynamicLoading(SchedulerDataLoader.DynamicalLoadingMode.Day);
scheduler.LoadData = true;
scheduler.EnableDataprocessor = true;
return View(scheduler);
}
Loading data
// C#
public ContentResult Data(DateTime from, DateTime To)
{
var data = new SchedulerAjaxData();
data.Add(this.db
.Events
.Where(e => e.start_date < from && e.end_date > to)
.ToList()
);
// select sections by required condition and add them to ServerList
// using the same name specified in Timeline configuration
data.ServerList.Add("timeline", new List<object>{
new {key = 1, label = "row 1"},
new {key = 2, label = "row 2"},
new {key = 3, label = "row 3"}
});
return (ContentResult)data;
}
Scripts/scheduler-config.js
// JS
scheduler.attachEvent("onBeforeViewChange", function (oldMode, oldDate, mode, date) {
if (oldMode != mode || +oldDate != +date)
scheduler.clearAll();
return true;
});
Such issue may be caused by Foreign Keys and cross-references between models that create circular structure which can't be serialized to JSON. For example, consider the following EF models:
public class Event
{
public int id{get;set;}
public string text{get;set;}
public DateTime start_date{get;set;}
public DateTime end_date {get;set;}
public int WorkplaceId {get;set;}
public Workplace Workplace {get;set;}
}
public class Workplace
{
public int Id {get; set;};
public string Name {get; set;}
public ICollection<Event> Events {get; set;}
}
Which are connected via the WorkplaceId foreign key. Such model can't be serialized as is due to circular references Event.Workplace <-> Workplace.Events. Thus, object properties has to be excluded from the dataset. For this, you can choose one of the two ways below:
using DHTMLX.Scheduler;
public class Event
{
public int id{get;set;}
public string text{get;set;}
public DateTime start_date{get;set;}
public DateTime end_date {get;set;}
public int WorkplaceId {get;set;}
[DHXJson(Ignore=true)]
public Workplace Workplace {get;set;}
}
public ContentResult Data()
{
var data = new SchedulerAjaxData(
this.db // this.db - instance of database context
.Events
.Select(e => new {e.id, e.text, e.start_date, e.end_date, e.WorkplaceId)
.ToList()
);
return (ContentResult)data;
}