Custom Edit Form

This demo illustrates how to change the Bootstrap Scheduler control UI. You can access the customization options for various dialogs using the OptionsDialogs property. In this demo, the OptionsDialogs.AppointmentDialog is used to customize the Edit Appointment form.

Create a new appointment or right-click on any existing appointment and choose the Open item, to see a custom Edit Appointment form in place of the standard form. This form not only allows you to edit standard appointment properties, but also the ones obtained via custom mappings.

Note that data field mappings should be specified before the GenerateDefaultLayoutElements method is called since only mapped appointment fields are generated by default.

<dx:BootstrapScheduler ID = "SchedulerCustomEditForm" runat="server"
    AppointmentDataSourceID="AppointmentDataSource" ResourceDataSourceID="efResourceDataSource"
    ActiveViewType="WorkWeek" GroupType="Resource">
    <OptionsResourceNavigator EnableIncreaseDecrease="false" />
    <Storage>
        <Resources>
            <CustomFieldMappings>
                <dx:ASPxResourceCustomFieldMapping Name="Phone" Member="Phone" />
                <dx:ASPxResourceCustomFieldMapping Name="Photo" Member="PhotoBytes" />
            </CustomFieldMappings>
        </Resources>
    </Storage>
    <Views>
        <DayView ResourcesPerPage="2" DayCount="2" />
        <WorkWeekView ResourcesPerPage="1" />
        <FullWeekView Enabled="true" ResourcesPerPage="1"/>
        <WeekView Enabled="false"/>
        <MonthView ResourcesPerPage="1" />
        <TimelineView ResourcesPerPage="2" />
    </Views>
</dx:BootstrapScheduler>
protected void Page_Init(object sender, EventArgs e) {
    var dialog = SchedulerCustomEditForm.OptionsDialogs.AppointmentDialog.UseViewModel(() => new CustomAppointmentEditDialogViewModel(SchedulerCustomEditForm));
    SchedulerDataHelper.SetupDefaultMappings(SchedulerCustomEditForm);
    dialog.GenerateDefaultLayoutElements();
    var resourceContainerGroup = dialog.LayoutElements.CreateGroup("resourceContainer", (g) => {
        g.ColSpanXl = 12;
        g.LayoutElements.CreateGroup("resourceGroup", (resourceGroup) => {
            var resourceIds = dialog.FindLayoutElement("ResourceIds");
            resourceIds.ColSpanXl = 12;
            resourceGroup.ColSpanXl = 6;
            resourceGroup.LayoutElements.Add(resourceIds);
        });
        g.LayoutElements.CreateGroup("resourceDetailGroup", (detailGroup) => {
            detailGroup.ColSpanXl = 6;
            detailGroup.LayoutElements.CreateField(m => m.Phone).ColSpanXl = 12;
            detailGroup.LayoutElements.CreateField(m => m.Photo).ColSpanXl = 12;
        });
    });
    dialog.InsertAfter(resourceContainerGroup, dialog.FindLayoutElement("StatusKey"));
}
using DevExpress.Web;
using DevExpress.Web.Bootstrap;
using DevExpress.Web.ASPxScheduler.Dialogs;
using DevExpress.Web.ASPxScheduler.Internal;
using DevExpress.XtraScheduler;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
public class CustomAppointmentEditDialogViewModel : AppointmentEditDialogViewModel<CustomAppointmentEditDialogViewModel> {
    public CustomAppointmentEditDialogViewModel(BootstrapScheduler scheduler) : base() {
        Scheduler = scheduler;
    }
    [DialogFieldViewSettings(Caption = "Phone"), ReadOnly(true)]
    public string Phone { get; set; }
    [DialogFieldViewSettings(Caption = "Photo")]
    public byte[] Photo { get; private set; }
    [DialogFieldViewSettings(Caption = "Doctor", EditorType = DialogFieldEditorType.ComboBox)]
    public override List<object> ResourceIds { get { return base.ResourceIds; } }
    [DialogFieldViewSettings(Caption = "Department", EditorType = DialogFieldEditorType.ComboBox)]
    public override string Location {
        get { return base.Location; }
        set { base.Location = value; }
    }
    protected BootstrapScheduler Scheduler { get; private set; }
    public override void Load(AppointmentFormController appointmentController) {
        base.Load(appointmentController);
        SetDataItemsFor(m => m.Location, PopulateLocations);
        UpdateResourceRelatedProperties();
        TrackPropertyChangeFor(m=> m.ResourceIds, UpdateResourceRelatedProperties);
    }
    void PopulateLocations(AddDataItemMethod<string> addDataItemDelegate) {
        addDataItemDelegate("Hospital", "Hospital");
        addDataItemDelegate("Surgery", "Surgery");
        addDataItemDelegate("Urgent Care", "Urgent Care");
        addDataItemDelegate("Pharmacy", "Pharmacy");
    }
    void UpdateResourceRelatedProperties() {
        if(ResourceIds.Any()) {
            Resource resource = Scheduler.Storage.Resources.GetResourceById(ResourceIds.First());
            Phone = resource.CustomFields["Phone"] as string;
            Photo = resource.CustomFields["Photo"] as byte[];
        }
    }
    public override void SetDialogElementStateConditions() {
        base.SetDialogElementStateConditions();
        SetItemVisibilityCondition(m => m.Description, false);
    }
}

Custom Appointments

This demo illustrates how to customize appointments' appearance. In this demo, the InitAppointmentDisplayText event is handled to apply custom formating to appointments' displayed texts. Additionally, the InitAppointmentImages event is used to display custom images based on appointment properties.

<dx:BootstrapScheduler ID="SchedulerCustomAppointments" ClientInstanceName="schedulerCustomAppointments"
    runat="server" ActiveViewType="WorkWeek" GroupType="Resource"
    OnInitAppointmentDisplayText="SchedulerCustomAppointments_InitAppointmentDisplayText"
    OnInitAppointmentImages="SchedulerCustomAppointments_InitAppointmentImages"
    AppointmentDataSourceID="AppointmentDataSource" ResourceDataSourceID="efResourceDataSource">
    <OptionsResourceNavigator EnableIncreaseDecrease="false" />
    <Views>
        <DayView ResourcesPerPage="2" DayCount="2" />
        <WorkWeekView ResourcesPerPage="1" />
        <FullWeekView Enabled="true" ResourcesPerPage="1" />
        <WeekView Enabled="false" />
        <MonthView ResourcesPerPage="1" />
        <TimelineView ResourcesPerPage="2" />
    </Views>
</dx:BootstrapScheduler>
protected void SchedulerCustomAppointments_InitAppointmentDisplayText(object sender, AppointmentDisplayTextEventArgs e) {
    Appointment apt = e.Appointment;
    e.Text = String.Format("[{0}] {1}", apt.Location, apt.Subject);
    e.Description = String.Format("Details: {0}", apt.Description);
}
protected void SchedulerCustomAppointments_InitAppointmentImages(object sender, DevExpress.Web.ASPxScheduler.AppointmentImagesEventArgs e) {
    Appointment apt = e.Appointment;
    AppointmentImageInfoCollection c = e.ImageInfoList;
    c.Clear();
    c.Images = CustomAppointmentsHelper.CustomImages;
    if(e.Appointment.IsRecurring)
        CustomAppointmentsHelper.AddRecurrentAppointmentImages(c, e.Appointment.IsException);
    else
        CustomAppointmentsHelper.AddNotRecurrentAppointmentImages(c);
}
using System.Web.UI.WebControls;
using DevExpress.Web;
using DevExpress.Web.ASPxScheduler.Drawing;
public static class CustomAppointmentsHelper {
    public const string ImagePath = "~/Content/CustomAppointments/";
    public const int CustomImagesOccurenceIndex = 0;
    public const int CustomImagesExceptionIndex = 1;
    public const int CustomImagesBizTripIndex = 2;
    public const int CustomImagesHolidayIndex = 3;
    static ImagePropertiesCollection customImages;
    public static ImagePropertiesCollection CustomImages {
        get {
            if(customImages == null)
                customImages = CreateCustomImages();
            return customImages;
        }
    }
    static ImagePropertiesCollection CreateCustomImages() {
        ImagePropertiesCollection images = new ImagePropertiesCollection();
        AddCustomImage(images, "CustomReccurence.png");
        AddCustomImage(images, "CustomException.png");
        AddCustomImage(images, "CustomBizTrip.png");
        AddCustomImage(images, "CustomHoliday.png");
        return images;
    }
    static void AddCustomImage(ImagePropertiesCollection target, string fileName) {
        string url = ImagePath + fileName;
        ImageProperties image = new ImageProperties(url);
        image.Width = Unit.Pixel(15);
        image.Height = Unit.Pixel(15);
        target.Add(image);
    }
    public static void AddNotRecurrentAppointmentImages(AppointmentImageInfoCollection c) {
        AddImageByIndex(c, CustomImagesBizTripIndex);
        AddImageByIndex(c, CustomImagesHolidayIndex);
    }
    public static void AddRecurrentAppointmentImages(AppointmentImageInfoCollection c, bool isException) {
        if(isException) {
            AddImageByIndex(c, CustomImagesExceptionIndex);
            AddImageByUrl(c, "~/Content/CustomAppointments/Warning.png", Unit.Pixel(24), Unit.Pixel(24));
        }
        else
            AddImageByIndex(c, CustomImagesOccurenceIndex);
    }
    static void AddImageByIndex(AppointmentImageInfoCollection c, int index) {
        AppointmentImageInfo info = new AppointmentImageInfo();
        info.ImageIndex = index;
        c.Add(info);
    }
    static void AddImageByUrl(AppointmentImageInfoCollection c, string url, Unit width, Unit height) {
        ImageProperties imageProperties = new ImageProperties(url);
        imageProperties.Width = width;
        imageProperties.Height = height;
        AppointmentImageInfo info = new AppointmentImageInfo();
        info.ImageProperties = imageProperties;
        c.Add(info);
    }
    static void AddImageByType(AppointmentImageInfoCollection c, DevExpress.XtraScheduler.Drawing.AppointmentImageType type) {
        c.AddStandard(type);
    }
}

Custom Context Menu

This demo illustrates how to customize a popup menu - a menu, invoked by right-clicking the Scheduler control or via the smart tag.

In this example, default scheduler menu items are replaced with custom ones. Instead of commands that enable you to create a new appointment or change its label and status, the popup menu contains submenu items that allow the creation of predefined events.

To achieve this, handle the PopupMenuShowing event. It provides access to a BootstrapSchedulerPopupMenu class instance, which represents the popup menu being displayed, and contains methods for modifying menu items and structure.

<dx:BootstrapScheduler ID="SchedulerCustomContextMenu" runat="server" GroupType="Resource" ActiveViewType="WorkWeek"
    OnPopupMenuShowing="SchedulerCustomContextMenu_PopupMenuShowing"
    OnCustomCallback="SchedulerCustomContextMenu_CustomCallback"
    AppointmentDataSourceID="AppointmentDataSource" ResourceDataSourceID="efResourceDataSource">
    <ClientSideEvents MenuItemClicked="OnMenuItemClicked" />
    <OptionsResourceNavigator EnableIncreaseDecrease="false" />
    <Views>
        <DayView ResourcesPerPage="2" DayCount="2" />
        <WorkWeekView ResourcesPerPage="1" />
        <FullWeekView Enabled="true" ResourcesPerPage="1" />
        <WeekView Enabled="false" />
        <MonthView Enabled="false" />
        <TimelineView ResourcesPerPage="2" />
    </Views>
</dx:BootstrapScheduler>
protected void SchedulerCustomContextMenu_PopupMenuShowing(object sender, BootstrapSchedulerPopupMenuShowingEventArgs e) {
    var menu = e.Menu;
    var menuItems = menu.Items;
    if(menu.MenuId.Equals(SchedulerMenuItemId.DefaultMenu)) {
        CustomMenuHelper.ClearUnusedDefaultMenuItems(menu);
        CustomMenuHelper.AddScheduleNewEventSubMenu(menu, "Schedule New Event", "NewEvent");
    }
    else if(menu.MenuId.Equals(SchedulerMenuItemId.AppointmentMenu)) {
        menu.Items.Clear();
        CustomMenuHelper.AddScheduleNewEventSubMenu(menu, "Change Event", "ChangeEvent");
        var deleteItem = new BootstrapMenuItem("Delete", "CustomItem_Delete");
        deleteItem.BeginGroup = true;
        menuItems.Add(deleteItem);
    }
}
protected void SchedulerCustomContextMenu_CustomCallback(object sender, CallbackEventArgsBase e) {
    var scheduler = (BootstrapScheduler)sender;
    string[] callbackParams = e.Parameter.Split(new char[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
    var command = callbackParams[0];
    var label = (CustomLabel)Enum.Parse(typeof(CustomLabel), callbackParams[1]);
    if(command == "ChangeEvent") {
        Appointment apt = scheduler.SelectedAppointments[0];
        CustomMenuHelper.UpdateAppointment(label, apt);
    }
    else if(command == "NewEvent")
        CustomMenuHelper.CreateAppointment(label, scheduler);
}
function OnMenuItemClicked(s, e) {
    var menuParameters = e.itemName.split("_");
    if (menuParameters[0] === "CustomItem") {
        e.handled = true;
        if (menuParameters[1] === "Delete" && confirm("Delete this appointment?")) {
            var selectedAppointment = s.GetAppointmentById(s.GetSelectedAppointmentIds()[0]);
            s.DeleteAppointment(selectedAppointment);
        }
        if (menuParameters.length > 2)
            s.PerformCallback(menuParameters[1] + "_" + menuParameters[2]);
    }
}
using System.Collections.Generic;
using DevExpress.Web.Bootstrap;
using DevExpress.Web.ASPxScheduler.Internal;
using DevExpress.XtraScheduler;
public enum CustomLabel { Routine = 1, FollowUp = 2, Urgent = 3, LabTesting = 4, Service = 5 }
public enum CustomStatus { Confirmed = 1, AwaitingConfirmation = 2, Cancelled = 3 }
public static class CustomMenuHelper {
    public static void AddScheduleNewEventSubMenu(BootstrapSchedulerPopupMenu menu, string caption, string subMenuName) {
        var newWithTemplateItem = new BootstrapMenuItem(caption, "TemplateEvents");
        newWithTemplateItem.BeginGroup = true;
        menu.Items.Insert(0, newWithTemplateItem);
        AddTemplatesSubMenuItems(newWithTemplateItem, subMenuName);
    }
    static void AddTemplatesSubMenuItems(BootstrapMenuItem parentMenuItem, string subMenuName) {
        parentMenuItem.Items.Add(new BootstrapMenuItem("Routine", string.Format("CustomItem_{0}_Routine", subMenuName)));
        parentMenuItem.Items.Add(new BootstrapMenuItem("Follow-Up", string.Format("CustomItem_{0}_FollowUp", subMenuName)));
        parentMenuItem.Items.Add(new BootstrapMenuItem("Urgent", string.Format("CustomItem_{0}_Urgent", subMenuName)));
        parentMenuItem.Items.Add(new BootstrapMenuItem("Lab Testing", string.Format("CustomItem_{0}_LabTesting", subMenuName)));
        parentMenuItem.Items.Add(new BootstrapMenuItem("Service", string.Format("CustomItem_{0}_Service", subMenuName)));
    }
    public static void ClearUnusedDefaultMenuItems(BootstrapSchedulerPopupMenu menu) {
        RemoveMenuItem(menu, "NewAppointment");
        RemoveMenuItem(menu, "NewAllDayEvent");
        RemoveMenuItem(menu, "NewRecurringAppointment");
        RemoveMenuItem(menu, "NewRecurringEvent");
        RemoveMenuItem(menu, "GotoToday");
        RemoveMenuItem(menu, "GotoDate");
    }
    static void RemoveMenuItem(BootstrapSchedulerPopupMenu menu, string menuItemName) {
        var item = menu.Items.FindByName(menuItemName);
        if(item != null)
            menu.Items.Remove(item);
    }
    public static void UpdateAppointment(CustomLabel label, Appointment apt) {
        if(label.Equals(CustomLabel.Service)) {
            apt.Subject = "Routine Maintenance";
            apt.Description = string.Empty;
            apt.StatusKey = (int)CustomStatus.Cancelled;
        }
        else if(label.Equals(CustomLabel.LabTesting)) {
            apt.Subject = "Lab Testing";
            apt.Description = string.Empty;
            apt.StatusKey = (int)CustomStatus.Confirmed;
        }
        else {
            apt.Subject = "Name";
            apt.Description = "Contact info:";
            apt.StatusKey = (int)CustomStatus.AwaitingConfirmation;
        }
        apt.LabelKey = (int)label;
    }
    public static void CreateAppointment(CustomLabel label, BootstrapScheduler scheduler) {
        Appointment apt = scheduler.Storage.CreateAppointment(AppointmentType.Normal);
        UpdateAppointment(label, apt);
        apt.Start = scheduler.SelectedInterval.Start;
        apt.End = scheduler.SelectedInterval.End;
        apt.ResourceId = scheduler.SelectedResource.Id;
        scheduler.Storage.Appointments.Add(apt);
    }
}
Screen Size
Color Themes
Demo QR Code