Delete Entire Series Options

5 posts, 0 answers
  1. Adam
    Adam avatar
    4 posts
    Member since:
    Apr 2014

    Posted 16 Nov 2014 Link to this post

    Good day,

    I am using the ASP.NET MVC server side scheduler.  What is noticeably missing is a way, when one chooses to delete/edit an entire series of a recurring event, to specify

    1.  Delete this entire series, or
    2.  Delete the highlighted event and all future events in the series, but leave previous ones intact.

    I understand how, behind the scenes, the original series would have to have its end date adjusted to the date prior to the selected event, and then a new series would have to be created with the new recurring event data.

    Are there plans to add this functionality to the current delete/edit recurring event dialog?  Or are there examples of how one would accomplish this?

    Still loving the scheduler, and thanks in advance for any assistance with this.

    Adam
  2. Georgi Krustev
    Admin
    Georgi Krustev avatar
    3707 posts

    Posted 18 Nov 2014 Link to this post

    Hello Adam,

    We do not have any immediate plans for the required functionality. Do you mind open a UserVoice discussion on the subject? This will help us to gather a valuable feedback for the feature.

    With regards to the other question, the required behavior probably could be accomplished by manipulating the event in the save/remove event handler of the scheduler. Once the event is saved/removed you can modify the recurrenceRule  field and thus achieve your goal.

    Regards,
    Georgi Krustev
    Telerik
     

    Check out the Telerik Platform - the only platform that combines a rich set of UI tools with powerful cloud services to develop web, hybrid and native mobile apps.

     
  3. UI for ASP.NET MVC is VS 2017 Ready
  4. Adam
    Adam avatar
    4 posts
    Member since:
    Apr 2014

    Posted 29 Jan 2015 in reply to Georgi Krustev Link to this post

    Good day,

    I have been able to handle the Remove event, and can see how adding additional data to the data passed to the server would accomplish what I want.  There is a snag, however, and I am hoping you could provide a little guidance.

    In the onRemove handler, it appears I have two options, preventDefault or let the handler continue.  I need user input, however.  Throwing up a dialog does not make the handler "wait" until the user has dismissed a dialog.  I have chosen not to use a confirm dialog.  I was thinking something more custom like a Kendo Window.  Javascript doesn't have a reliable Wait condition, where I can prevent the onRemove handler from finishing until I have acquired the user's delete notion.

    I could add my delete options to the custom delete dialog supplied by the scheduler, but I don't see a template for that, and I would somehow have to communicate the user's choice to the remove handler.

    How would you recommend going about this?

    Thanks in advance,
    Adam
  5. Georgi Krustev
    Admin
    Georgi Krustev avatar
    3707 posts

    Posted 02 Feb 2015 Link to this post

    Hello Adam,

    In general, the best way to block a method execution is to use Html confirm dialog. The custom JavaScript solution will not wait for the user's input and the method will continue its execution.

    I suppose that you can open a modal window from onRemove event handler with a custom form, but in this case you will need to prevent the function anyway and then perform the data source action manually.

    I would suggest you use the available Html dialogs as they can block function execution and thus give you the desired user input.

    If you still experiencing any difficulties to implement your requirement, I will need more details about it in order to assist you further.

    Regards,
    Georgi Krustev
    Telerik
     

    Check out the Telerik Platform - the only platform that combines a rich set of UI tools with powerful cloud services to develop web, hybrid and native mobile apps.

     
  6. Adam
    Adam avatar
    4 posts
    Member since:
    Apr 2014

    Posted 02 Feb 2015 in reply to Georgi Krustev Link to this post

    Thanks for your reply, Georgi!

    I have a solution that went a slightly different direction, but one I really like, and I will post the relevant code here, in case someone else wants to do this.  I think this is a pretty critical feature, as most prime time calendar applications have some sort of "delete this and future events" in a series.  Ok, here goes...

    1.  First things first.  Add a Remove event handler in the Razor view to the scheduler...
    .Events(e =>
    {
        e.Remove("onRemove");
    })


    2.  Implement onRemove in the javascript file that accompanies the View...

    function onRemove(e) {
        // On a remove, open the window to select delete options...
        if (e.event.recurrenceRule) {
            recurringEvent = e.event;
            var window = $("#seriesDeleteOption").data("kendoWindow");
            window.center();
            window.refresh();
            window.open();
            e.preventDefault();
        }
    }

    First, I only need to do this on recurring rule events.  If so, I capture the event in a global variable, as I will need it later for the result of the dialog I create in order to ask which delete option the user wants.  Speaking of which, open that dialog, centering and refreshing it.  The preventDefault is there to make sure nothing happens with the event yet on the server.

    3.  I need to know which event the user clicked from which I want to start the delete in the series, so capture that with another event handler on the scheduler, some javascript, and a global javascript variable for the date of the actual event clicked.  Here are the two global variables in the JS file.

    var selectedDate = null;
    var recurringEvent = null;

    Here are the two events now on the scheduler...
    .Events(e =>
    {
        e.Remove("onRemove");
        e.Change("onChange");
    })

    function onChange(e) {
        // Store the selected event's date, in case we want to delete a series from this date forward...
        selectedDate = e.start;
    }

    Keep in mind, your scheduler needs to have selection enabled.  Add this to your Html.Kendo().Scheduler<>()
    .Selectable(true)

    4.  Define the Kendo dialog box somewhere in your View file.  This dialog will show your sweet new delete options and be launched by the above javascript!
    @(Html.Kendo().Window()
        .Name("seriesDeleteOption")
        .Title("Delete Series")
        .LoadContentFrom("DeleteOptionView", "Instructor")
        .Modal(true)
        .Draggable()
        .Visible(false)
        .Width(400)
        .Resizable()
        .Position(position => position.Left(100).Top(100)))

    5.  Add the controller action that supplies the HTML for the view.  This code isn't exciting, but it doesn't have to be.
    public ActionResult DeleteOptionView()
    {
        return PartialView("_DeleteSeriesOptions");
    }

    6.  The actual partial view.
    <div>
        <div class="deleteSeriesOption">
            <input type="radio" name="deleteSeriesOption" value="0" checked="checked" /><label for="0">Delete this event and future events in the series</label>
        </div>
        <div class="deleteSeriesOption">
            <input type="radio" name="deleteSeriesOption" value="1" /><label for="0">Delete all events in the series</label>
        </div>
        <div class="deleteSeriesSubmit">
            <a id="submitDeleteChoice" onclick="closeDeleteOptions()">Continue</a>
            <a id="cancelDeleteChoice" onclick="cancelDeleteOptions()">Cancel</a>
        </div>
    </div>

    7. Here is the cancel and delete javascript handlers for the two links at the bottom of the custom dialog.  Right now, I have two options, delete the whole series, or just the event clicked and all subsequent events.

    function closeDeleteOptions(e) {
        var dialog = $("#seriesDeleteOption").data("kendoWindow");
        var theDate = selectedDate;
        var theEvent = recurringEvent;
     
        // Now get the event and remove it, adding the data so the server sees it...
        dialog.close();
     
        if (theDate != null && theEvent != null) {
            var scheduler = $("#scheduler").data("kendoScheduler");
            // If we want this one and forward deleted, pass extra goodies to the controller...
            if ($("input[name=deleteSeriesOption]:checked").val() == 0) {
                theEvent.DeleteForwardOnly = true;
                theEvent.DeleteForwardDate = theDate.toDateString();
            }
     
            // We are ready to send the event back to the server for deletion...
            // scheduler.removeEvent() will display the "delete series" dialog...
            // to get around that, I alter the datasource with the item directly and sync...
            scheduler.dataSource.remove(theEvent);
            scheduler.dataSource.sync();
            setTimeout(function () { scheduler.dataSource.read(); }, 100);
        }
    }
     
    function cancelDeleteOptions(e) {
        var window = $("#seriesDeleteOption").data("kendoWindow");
        window.close();
    }

    This is pretty important code.  If everything important is not null, we are going to add the clicked event date (and a boolean I probably don't need) to the event payload that will go back to the server for the Destroy handler.  Calling removeEvent on the scheduler directly, at this point, will launch the "delete this occurrence or series" Kendo dialog, and we are past that point already.  So, I opted to remove the event entirely from the datasource and sync it with the server.  This has the desired effect of invoking the Destroy event on the server.  However, if the user selected "just this event and on..." I want the scheduler to display those events again, and hence the scheduler.dataSource.read() to refresh the view.

    Here is the field code to add the extra two fields to the datasource in the View.  You will have other fields on your schedule object, but for the sake of completeness...
    .DataSource(d => d
                .Model(m =>
                {
                    m.Id(f => f.EventId);
                    m.Field(f => f.Title);
                    m.Field(f => f.Description);
                    m.Field(f => f.Start);
                    m.Field(f => f.End);
                    m.Field(f => f.RecurrenceID);
                    m.Field(f => f.RecurrenceRule);
                    m.Field(f => f.RecurrenceException);
                    m.Field(f => f.IsAllDay);
                    m.Field(f => f.DeleteForwardOnly);
                    m.Field(f => f.DeleteForwardDate);
                })
                .Read("ReadEvents", "Instructor")
                .Create("CreateEvent", "Instructor")
                .Destroy("DestroyEvent", "Instructor")
                .Update("UpdateEvent", "Instructor")
                .Events(e => e.Error("error_handler"))
            )

    8.  Ok, now the hard part, and the fun part.  The server needs to take the data and alter the event's recurrence properties if the user selected "delete this and future..."  Here is my DestroyEvent code, minus stuff specific to my business.
    public virtual JsonResult DestroyEvent([DataSourceRequest] DataSourceRequest request, ScheduleEvent scheduledEvent)
    {
        if (ModelState.IsValid)
        {
            try
            {
                if (scheduledEvent.DeleteForwardOnly && !String.IsNullOrEmpty(scheduledEvent.DeleteForwardDate))
                {
                    // Just update the event to change it's end date to the day before...
                    DateTime dateToStartDeleting = DateTime.MinValue;
                    if (DateTime.TryParse(scheduledEvent.DeleteForwardDate, out dateToStartDeleting))
                    {
                        // Adjust the end date (with time) until it's the day before the series is to be nuked...
                        var untilDate = scheduledEvent.End;
                        untilDate = untilDate.AddDays((dateToStartDeleting - untilDate).TotalDays);
     
                        // Adjust the recurrence rule to go a day before the day the series was deleted til...
                        scheduledEvent.RecurrenceRule = AdjustRecurrenceRule(scheduledEvent.RecurrenceRule, untilDate);
                        InstructionalEvent.Update(scheduledEvent.ToInstructionalEvent());
                    }
                }
                else
                {
                    InstructionalEvent.Delete(scheduledEvent.EventId);
                }
            }
            catch (Exception ex)
            {
                Error.Create(ex);
                throw;
            }
        }
     
        return Json(new[] { scheduledEvent }.ToDataSourceResult(request, ModelState));
    }

    Basically, I just want to adjust the recurrence rule to contain an "UNTIL=" item set to the day the user chose to end the series.  That is the crux of my logic.  All the rest of the solution has been to get my code to the point where on the server, I can adjust the recurrence rule of the event as such.  The scheduledEvent.End property contains the ending time for the event on the date on which the event starts.  So that part ended up being just adding days based on the date the user selected.  I figured I had to remove any existing "COUNT=" or "UNTIL=" that already existed on the event, and replace them with the new one from this helper function.

    protected string AdjustRecurrenceRule(string originalRecurrence, DateTime newEndDate)
    {
        string newRule = originalRecurrence;
        var newList = originalRecurrence.Split(';').ToList();
         
        // Get rid of any existing UNTIL rules...
        if (newList.Any(r => r.Contains("UNTIL=")))
        {
            var index = newList.IndexOf(newList.Single(r => r.Contains("UNTIL=")));
            newList.RemoveAt(index);
        }
     
        // Get rid of any existing COUNT rules...
        if (newList.Any(r => r.Contains("COUNT=")))
        {
            var index = newList.IndexOf(newList.Single(r => r.Contains("COUNT=")));
            newList.RemoveAt(index);
        }
     
        newList.Add("UNTIL=" + newEndDate.ToUniversalTime().ToString(Constants.KendoDateFormat));
        newRule = String.Join(";", newList);
     
        return newRule;
    }

    And my sweet Kendo date formatting constant...

    public const string KendoDateFormat = "yyyyMMdd'T'HHmmss'Z'";

    That's about it.  I like the solution because (1) it seems to work and (2) the code is fairly straightforward and could be extended if other options were required.

    I hope this helps if someone else is looking for the same functionality from the Kendo scheduler, which has been an enormous time saver and extremely easy to extend.

    Adam
Back to Top
UI for ASP.NET MVC is VS 2017 Ready