#109616
Markov
Keymaster
The guide below details how to implement Custom Window Editor for Scheduler in Blazor.

In the cases when you need to create additional custom fields, which are not supported by the default Editing Window, Smart.Scheduler allows you to replace it with an entirely custom one, which will handle the update/insert operations instead.

Create Scheduler and Custom Window
First, we need to create our Scheduler. The code below setups a basic Scheduler with multiple Events:

<Scheduler @ref=scheduler DataSource="dataSource" View="SchedulerViewType.Month"></Scheduler>

@code{
    Scheduler scheduler;
    List<SchedulerDataSource> dataSource = new List<SchedulerDataSource>()
        {
            new SchedulerDataSource()
            {
                Id = "1",
                Label = "Google AdWords Strategy",
                DateStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day+1, 10, 0, 0),
                DateEnd = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day +1, 11, 30, 0),
                BackgroundColor = "#E67C73",

            },
            new SchedulerDataSource()
            {
                Id = "3",
                Label = "New Brochures",
                DateStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day+3, 12, 0, 0),
                DateEnd = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day+3, 15, 0, 0),
                BackgroundColor = "#8E24AA"
            },
            new SchedulerDataSource()
            {
                Id = "3",
                Label = "Brochure Design Review",
                DateStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day+6, 15, 30, 0),
                DateEnd = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day+6, 18, 15, 0),
            },
    };

}
Then we should add the HTML of our Custom Editing Window. The Window in this demo features a Text Input, DateTime Pickers, DropDownList inputs and a Star Rating Input:

<Window @ref="window" Modal="true">
    <h3>@heading</h3>
    <table>
        <tr>
            <td>
                <div class="smart-control">
                    <label>Label<br /></label>
                    <Input @ref="labelInput" Placeholder="Event label"></Input>
                </div>
            </td>
            <td></td>
        </tr>
        <tr>
            <td>
                <div class="smart-control">
                    <label>Date Start</label>
                    <DateTimePicker @ref="dateStartInput" FormatString="D" CalendarButton="true"
                                            DropDownAppendTo="body"></DateTimePicker>
                </div>
            </td>
            <td>
                <div class="smart-control">
                    <label>Date End</label>
                    <DateTimePicker @ref="dateEndInput" FormatString="D" CalendarButton="true"
                                    DropDownAppendTo="body"></DateTimePicker>
                </div>
            </td>
        </tr>
        <tr>
            <td>
                <div class="smart-control">
                    <label>Department</label>
                    <DropDownList @ref="departmentInput">
                        <ListItem Value="sales">Sales</ListItem>
                        <ListItem Value="marketing">Marketing</ListItem>
                        <ListItem Value="finance">Finance</ListItem>
                        <ListItem Value="technical">IT</ListItem>
                    </DropDownList>
                </div>
            </td>
            <td>
                <div class="smart-control">
                    <label>Office Location</label>
                    <DropDownList @ref="locationInput">
                        <ListItem Value="new_york">New York</ListItem>
                        <ListItem Value="los_angeles">Los Angeles</ListItem>
                        <ListItem Value="chicago">Chicago</ListItem>
                        <ListItem Value="houston">Houston</ListItem>
                    </DropDownList>
                </div>
            </td>
        </tr>
        <tr>
            <td>
                <div class="smart-control">
                    <label>Background Color</label>
                    <ColorInput @ref="backgroundColorInput"></ColorInput>
                </div>
            </td>
            <td>
                <div class="smart-control">
                    <label>Rating </label>
                    <Rating @ref="ratingInput"></Rating>
                </div>
            </td>
        </tr>
    </table>
    <div style="margin-top: 20px;">
        <Button @ref="cancelButton" OnClick="CloseWindow">Cancel</Button>
        <Button @ref="saveButton" OnClick="SaveChanges">Save</Button>
    </div>
</Window>

@code{
    Scheduler scheduler;
    Window window;
    string heading = "Create an Event";
    Input labelInput;
    DateTimePicker dateStartInput, dateEndInput;
    DropDownList departmentInput, locationInput;
    ColorInput backgroundColorInput;
    Rating ratingInput;
    Button cancelButton, saveButton;

    bool editing = false; int editingIndex = -1;
}
Finally, add the CSS to format the content inside:

smart-window {
    --smart-window-default-height: 450px;
    --smart-window-default-width: 600px;
    --smart-window-header-height: 35px;
}

.smart-window .smart-content-container>.smart-content {
    align-items: baseline;
}

#repeat-editor {
    margin-top: 10px;
}

#repeat-editors>div {
    margin-top: 10px;
}

#repeat-editors smart-date-time-picker {
    width: 100px;
}

#repeat-editors smart-number-input {
    width: 100px;
}

smart-radio-button {
    margin-left: 10px;
    padding: 5px;
}

.radio-button-wrapper {
    display: flex;
    align-items: center;
    margin-top: 5px;
}

.submit-btn {
    margin-left: 15px;
    --smart-background: #005b98;
    --smart-background-color: white;
}

table td {
    padding-top: 10px;
}
Replace Default Window
In order to replace the default Window, we will need to use onReady and the OnEditDialogOpening event.

In the OnReady Event, use JSInterop to call the "preventWindowOpening" JS function, which we will later create.
@inject IJSRuntime JS

<Scheduler @ref=scheduler DataSource="dataSource" View="SchedulerViewType.Month" OnEditDialogOpening="OnEditDialogOpening" OnReady="OnReady"></Scheduler>

@code{
    ...
    private void OnReady(Scheduler scheduler)
    {
        JS.InvokeVoidAsync("preventWindowOpening");
    }
}
In the _Host.cshtml, add the "preventWindowOpening" JS function, which will simply prevent the default window from opening:
<script>
    (function (global) {
        global.preventWindowOpening = function () {
            const scheduler = document.querySelector('smart-scheduler');
            scheduler.addEventListener('editDialogOpening', function(event){
                event.preventDefault();
            });
        }
    })(window);
</script>
Next, attach the "OnEditDialogOpening" event to the Scheduler.
The function will first check whether the users is attempting to insert a new Event or modify an existing one. If the operation is insertion, it will open an empty Editing Window.
If it is editing, the function will find the index of the selected Event and open the Editing Window with the Event's data filled-in.
private async void OnEditDialogOpening(Event ev){
    SchedulerEditDialogOpeningEventDetail args = ev["Detail"];
    Dictionary<string, string> item = args.Item.ToObject<Dictionary<string, string>>();
    editing = item.ContainsKey("label");
    if (editing)
    {
        FillWindow(item);
        var events = await scheduler.GetDataSourceAsync();
        //find the index of the item in the events array using the id property
        for(int i = 0; i < events.Count(); i++)
        {
            //System.Text.Json.JsonElement currentEvent = events[i];
            SchedulerDataSource currentEvent = events.ElementAt(i) as SchedulerDataSource;
            if (item["id"] == currentEvent.Id)
            {
                editingIndex = i;
            }
        }
    }
    else
    {
        ResetWindow(item["dateStart"], item["dateEnd"]);
    }
    window.Open();

}
Custom Window
Repeating Events
Smart.Scheduler Events can also be set as repeating. For example, a birthday reminder every year or a work meeting every Monday morning. Now we will add the editors, which will allow the user to set the repeating options.

function repeatChange(event) {
    const checked = event.detail.value;
    const element = document.querySelector('#editing-window "#repeat-editor"');
    if (checked) {
        element.style.display = "block";
    } else {
        element.style.display = "none";
    }
    setRepeatEditor("daily");
}

function repeatFreqChange(event) {
    let selectedValue = event.detail.value.toLowerCase();
    this.setRepeatEditor(selectedValue);
}

function setRepeatEditor(type) {
    const window = document.querySelector('#editing-window "#repeat-editor"');
    window.innerHTML = "";
    const repeatFreqEditor = document.createElement("div");
    const repeatFreqLabel = document.createElement("div");
    let word;
    switch (type) {
        case "hourly":
            word = "hours";
            break;
        case "daily":
            word = "days";
            break;
        case "weekly":
            word = "weeks";
            break;
        case "monthly":
            word = "months";
            break;
        case "yearly":
            word = "years";
            break;
    }
    repeatFreqLabel.innerHTML = <code>Repeat every (${word})</code>;
    repeatFreqEditor.appendChild(repeatFreqLabel);
    const repeatInterval = document.createElement("smart-number-input");
    repeatInterval.id = "repeatInterval";
    repeatInterval.min = 1;
    repeatFreqEditor.appendChild(repeatInterval);

    window.appendChild(repeatFreqEditor);

    if (type === "weekly" || type === "monthly" || type === "yearly") {
        const repeatOnEditor = document.createElement("div");
        const repeatOnLabel = document.createElement("label");
        repeatOnLabel.innerHTML = "Repeat on ";
        repeatOnEditor.appendChild(repeatOnLabel);
        if (type === "weekly") {
            const repeatOn = document.createElement("smart-button-group");
            repeatOn.id = "repeatOn";
            repeatOn["selectionMode"] = "zeroOrMany";
            repeatOn.dataSource = [{
                    label: "Mon",
                    value: "1"
                },
                {
                    label: "Tue",
                    value: "2"
                },
                {
                    label: "Wed",
                    value: "3"
                },
                {
                    label: "Thu",
                    value: "4"
                },
                {
                    label: "Fri",
                    value: "5"
                },
                {
                    label: "Sat",
                    value: "6"
                },
                {
                    label: "Sun",
                    value: "0"
                }
            ];
            repeatOnEditor.appendChild(repeatOn);
        } else if (type === "monthly") {
            const repeatOn = document.createElement("smart-number-input");
            repeatOn.id = "repeatOn";
            repeatOn.min = 1;
            repeatOn.max = 31;
            repeatOnEditor.appendChild(repeatOn);
        } else if (type === "yearly") {
            repeatOnEditor.style.display = "flex";
            repeatOnEditor.style.alignItems = "center";
            const repeatOn = document.createElement("smart-drop-down-list");
            repeatOn.id = "repeatOn";
            repeatOn.dataSource = [{
                    label: "January",
                    value: "0"
                },
                {
                    label: "February",
                    value: "1"
                },
                {
                    label: "March",
                    value: "2"
                },
                {
                    label: "April",
                    value: "3"
                },
                {
                    label: "May",
                    value: "4"
                },
                {
                    label: "June",
                    value: "5"
                },
                {
                    label: "July",
                    value: "6"
                },
                {
                    label: "August",
                    value: "7"
                },
                {
                    label: "September",
                    value: "8"
                },
                {
                    label: "October",
                    value: "9"
                },
                {
                    label: "November",
                    value: "10"
                },
                {
                    label: "December",
                    value: "11"
                }
            ];
            repeatOnEditor.appendChild(repeatOn);
            const repeatOnDay = document.createElement("smart-number-input");
            repeatOnDay.id = "repeatOnDay";
            repeatOnDay.min = 1;
            repeatOnDay.max = 31;
            repeatOnEditor.appendChild(repeatOnDay);
        }

        window.appendChild(repeatOnEditor);
    }
    const endRepeatEditor = document.createElement("div");
    const endRepeatLabel = document.createElement("div");
    endRepeatLabel.innerHTML = "End repeat";
    endRepeatEditor.appendChild(endRepeatLabel);
    const neverWrapper = document.createElement("div");
    neverWrapper.classList.add("radio-button-wrapper");
    neverWrapper.innerHTML =
        "<smart-radio-button group-name='repeat' id='never' checked>Never</smart-radio-button>";
    endRepeatEditor.appendChild(neverWrapper);
    const onWrapper = document.createElement("div");
    onWrapper.classList.add("radio-button-wrapper");
    onWrapper.innerHTML =
        

<smart-radio-button group-name=’repeat’ id=’on’>On</smart-radio-button>
<smart-date-time-picker id=’dateOfRepeat’ formatString=”dd MM yyyy” calendar-button drop-down-append-to=”body” disabled></smart-date-time-picker>`;
endRepeatEditor.appendChild(onWrapper);
const afterWrapper = document.createElement(“div”);
afterWrapper.classList.add(“radio-button-wrapper”);
afterWrapper.innerHTML =
“<smart-radio-button group-name=’repeat’ id=’after’>After</smart-radio-button><smart-number-input id=’repeatAfter’ min=’1′ disabled></smart-number-input>occurrences”;
endRepeatEditor.appendChild(afterWrapper);
Array.from(endRepeatEditor.querySelectorAll(“smart-radio-button”)).forEach(
(button) => {
button.addEventListener(“checkValue”, (event) => {
const target = event.target;
//disable the other inputs
if (target.id === “never”) {
(onWrapper.querySelector(
“smart-date-time-picker”
)).disabled = true;
(afterWrapper.querySelector(
“smart-number-input”
)).disabled = true;
} else if (target.id === “on”) {
(onWrapper.querySelector(
“smart-date-time-picker”
)).disabled = false;
(afterWrapper.querySelector(
“smart-number-input”
)).disabled = true;
} else if (target.id === “after”) {
(onWrapper.querySelector(
“smart-date-time-picker”
)).disabled = true;
(afterWrapper.querySelector(
“smart-number-input”
)).disabled = false;
}
});
}
);
window.appendChild(endRepeatEditor);
}
Repeat Editors
Reset & Fill Window Content
The ResetWindow function is called when the user attempts to insert a new Event. It empties all fields and sets the date inputs to the time slot selected by the user:

private void ResetWindow(string dateStart, string dateEnd)
{
heading = “Create an Event”;
labelInput.Value = “”;
dateStartInput.SetDate(DateTime.ParseExact(dateStart, “MM/dd/yyyy HH:mm:ss”, null));
dateEndInput.SetDate(DateTime.ParseExact(dateEnd, “MM/dd/yyyy HH:mm:ss”, null));
departmentInput.SelectedIndexes = new int[] { 0 };
locationInput.SelectedIndexes = new int[] { 0 };
backgroundColorInput.Value = “”;
ratingInput.Value = 1;
}
The FillWindow receives a scheduler event and fill the event’s data inside the Winow Fields:

private void FillWindow(Dictionary schedulerEvent)
{
heading = “Edit an Event”;
labelInput.Value = schedulerEvent[“label”];
dateStartInput.SetDate(DateTime.ParseExact(schedulerEvent[“dateStart”], “MM/dd/yyyy HH:mm:ss”, null));
dateEndInput.SetDate(DateTime.ParseExact(schedulerEvent[“dateEnd”], “MM/dd/yyyy HH:mm:ss”, null));
departmentInput.Select(schedulerEvent.GetValueOrDefault(“department”, “sales”));
locationInput.Select(schedulerEvent.GetValueOrDefault(“location”, “new_york”));
backgroundColorInput.Value = schedulerEvent.GetValueOrDefault(“backgroundColor”, “”);
ratingInput.Value = int.Parse(schedulerEvent.GetValueOrDefault(“rating”, “1”));
}
Fill Window Content
Save Changes
Finally, we need to add the saveChanges() function which will be called when the user clicks the ‘Save’ button.
The function should take the values from the editors and cheched whether it should modify an existing Event or create a new one.

private async void SaveChanges()
{
object eventObject = new {
id = (DateTime.Now).ToString(“yyyyMMddHHmmssffff”),
label = labelInput.Value,
dateStart = await dateStartInput.GetDate(),
dateEnd = await dateEndInput.GetDate(),
department = departmentInput.SelectedValues[0],
location = locationInput.SelectedValues[0],
backgroundColor = backgroundColorInput.Value,
rating = ratingInput.Value
};
if (editing && editingIndex > -1)
{
scheduler.UpdateEvent(editingIndex, eventObject);
}
else
{
scheduler.AddEvent(eventObject);
}
window.Close();
}
Now, the custom Editing Window is complete. The full code is below:
@page “/”
@inject IJSRuntime JS
<PageTitle>Scheduler</PageTitle>

<Scheduler @ref=scheduler DataSource=”dataSource” View=”SchedulerViewType.Month” OnEditDialogOpening=”OnEditDialogOpening” OnReady=”OnReady”></Scheduler>

<Window @ref=”window” Modal=”true”>
<h3>@heading</h3>
<table>
<tr>
<td>
<div class=”smart-control”>
<label>Label<br /></label>
<Input @ref=”labelInput” Placeholder=”Event label”></Input>
</div>
</td>
<td></td>
</tr>
<tr>
<td>
<div class=”smart-control”>
<label>Date Start</label>
<DateTimePicker @ref=”dateStartInput” FormatString=”D” CalendarButton=”true”
DropDownAppendTo=”body”></DateTimePicker>
</div>
</td>
<td>
<div class=”smart-control”>
<label>Date End</label>
<DateTimePicker @ref=”dateEndInput” FormatString=”D” CalendarButton=”true”
DropDownAppendTo=”body”></DateTimePicker>
</div>
</td>
</tr>
<tr>
<td>
<div class=”smart-control”>
<label>Department</label>
<DropDownList @ref=”departmentInput”>
<ListItem Value=”sales”>Sales</ListItem>
<ListItem Value=”marketing”>Marketing</ListItem>
<ListItem Value=”finance”>Finance</ListItem>
<ListItem Value=”technical”>IT</ListItem>
</DropDownList>
</div>
</td>
<td>
<div class=”smart-control”>
<label>Office Location</label>
<DropDownList @ref=”locationInput”>
<ListItem Value=”new_york”>New York</ListItem>
<ListItem Value=”los_angeles”>Los Angeles</ListItem>
<ListItem Value=”chicago”>Chicago</ListItem>
<ListItem Value=”houston”>Houston</ListItem>
</DropDownList>
</div>
</td>
</tr>
<tr>
<td>
<div class=”smart-control”>
<label>Background Color</label>
<ColorInput @ref=”backgroundColorInput”></ColorInput>
</div>
</td>
<td>
<div class=”smart-control”>
<label>Rating </label>
<Rating @ref=”ratingInput”></Rating>
</div>
</td>
</tr>
</table>
<div style=”margin-top: 20px;”>
<Button @ref=”cancelButton” OnClick=”CloseWindow”>Cancel</Button>
<Button @ref=”saveButton” OnClick=”SaveChanges”>Save</Button>
</div>
</Window>

@code{
Scheduler scheduler;
Window window;
string heading = “Create an Event”;
Input labelInput;
DateTimePicker dateStartInput, dateEndInput;
DropDownList departmentInput, locationInput;
ColorInput backgroundColorInput;
Rating ratingInput;
Button cancelButton, saveButton;

bool editing = false; int editingIndex = -1;

List<SchedulerDataSource> dataSource = new List<SchedulerDataSource>()
{
new SchedulerDataSource()
{
Id = “1”,
Label = “Google AdWords Strategy”,
DateStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day+1, 10, 0, 0),
DateEnd = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day +1, 11, 30, 0),
BackgroundColor = “#E67C73”,

},
new SchedulerDataSource()
{
Id = “3”,
Label = “New Brochures”,
DateStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day+3, 12, 0, 0),
DateEnd = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day+3, 15, 0, 0),
BackgroundColor = “#8E24AA”
},
new SchedulerDataSource()
{
Id = “3”,
Label = “Brochure Design Review”,
DateStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day+6, 15, 30, 0),
DateEnd = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day+6, 18, 15, 0),
},
};

private void ResetWindow(string dateStart, string dateEnd)
{
heading = “Create an Event”;
labelInput.Value = “”;
dateStartInput.SetDate(DateTime.ParseExact(dateStart, “MM/dd/yyyy HH:mm:ss”, null));
dateEndInput.SetDate(DateTime.ParseExact(dateEnd, “MM/dd/yyyy HH:mm:ss”, null));
departmentInput.SelectedIndexes = new int[] { 0 };
locationInput.SelectedIndexes = new int[] { 0 };
backgroundColorInput.Value = “”;
ratingInput.Value = 1;
}

private void FillWindow(Dictionary<string, string> schedulerEvent)
{
heading = “Edit an Event”;
labelInput.Value = schedulerEvent[“label”];
dateStartInput.SetDate(DateTime.ParseExact(schedulerEvent[“dateStart”], “MM/dd/yyyy HH:mm:ss”, null));
dateEndInput.SetDate(DateTime.ParseExact(schedulerEvent[“dateEnd”], “MM/dd/yyyy HH:mm:ss”, null));
departmentInput.Select(schedulerEvent.GetValueOrDefault(“department”, “sales”));
locationInput.Select(schedulerEvent.GetValueOrDefault(“location”, “new_york”));
backgroundColorInput.Value = schedulerEvent.GetValueOrDefault(“backgroundColor”, “”);
ratingInput.Value = int.Parse(schedulerEvent.GetValueOrDefault(“rating”, “1”));
}

private async void OnEditDialogOpening(Event ev){
SchedulerEditDialogOpeningEventDetail args = ev[“Detail”];
Dictionary<string, string> item = args.Item.ToObject<Dictionary<string, string>>();
editing = item.ContainsKey(“label”);
if (editing)
{
FillWindow(item);
var events = await scheduler.GetDataSourceAsync();
//find the index of the item in the events array using the id property
for(int i = 0; i < events.Count(); i++)
{
//System.Text.Json.JsonElement currentEvent = events[i];
SchedulerDataSource currentEvent = events.ElementAt(i) as SchedulerDataSource;
if (item[“id”] == currentEvent.Id)
{
editingIndex = i;
}
}
}
else
{
ResetWindow(item[“dateStart”], item[“dateEnd”]);
}
window.Open();

}

private void OnReady(Scheduler scheduler)
{
JS.InvokeVoidAsync(“replaceWindow”);
}

private void CloseWindow()
{
window.Close();
}

private async void SaveChanges()
{
object eventObject = new {
id = (DateTime.Now).ToString(“yyyyMMddHHmmssffff”),
label = labelInput.Value,
dateStart = await dateStartInput.GetDate(),
dateEnd = await dateEndInput.GetDate(),
department = departmentInput.SelectedValues[0],
location = locationInput.SelectedValues[0],
backgroundColor = backgroundColorInput.Value,
rating = ratingInput.Value
};
if (editing && editingIndex > -1)
{
scheduler.UpdateEvent(editingIndex, eventObject);
}
else
{
scheduler.AddEvent(eventObject);
}
window.Close();
}

}

<style>
smart-window {
–smart-window-default-height: 450px;
–smart-window-default-width: 600px;
–smart-window-header-height: 35px;
}

.smart-window .smart-content-container>.smart-content {
align-items: baseline;
}

#repeat-editor {
margin-top: 10px;
}

#repeat-editors>div {
margin-top: 10px;
}

#repeat-editors smart-date-time-picker {
width: 100px;
}

#repeat-editors smart-number-input {
width: 100px;
}

smart-radio-button {
margin-left: 10px;
padding: 5px;
}

.radio-button-wrapper {
display: flex;
align-items: center;
margin-top: 5px;
}

.submit-btn {
margin-left: 15px;
–smart-background: #005b98;
–smart-background-color: white;
}

table td {
padding-top: 10px;
}
</style>`