Group Events by Date using KnockoutJS

2019-08-03 02:08发布

Update: I provided a best answer to my problem. Feel free to improve the readability and/or efficiency.


Problem: Using fullcalendar.io in my requireJS set up, Event objects (received from an AJAX call to a .ashx handler) are populated on the calendar on every next/prev/view change. I would like to use that JSON object on callback to create a list of Events grouped by the startDate property.

What I've done
On Calendar update, such as new month, I was able to set up a basic ViewModel to populate a new list of events with the observable array. The only part that is missing is the group by, such as this example. Since I'm looking to group by an Event object's start date and then sort each group ascending by start date, I'm not sure of the best practices I should take, such as if it is necessary to transform my JSON data into objects. I will continue to update this post as I learn more. The reason that I am not using the linked example is because I do not understand the complete terminology behind the handler, but do understand some keywords like $root at this point.

AJAX call and requireJS module load: fullcalendar

var fullCalendar = ".calendar-module",
    viewModel;

function initFullCalendar() {
    $(fullCalendar).fullCalendar({
            events: {
                url: '/Presentation/DotGov/test/fullcalendar/GetEvents.ashx',
                type: 'POST',
                success: function (data) {

                    // bind events to KO ViewModel object
                    viewModel.events(data);
                }
            },
    });
}

// requireJS module load
return {
    load: function () {

        viewModel = new ViewModel();
        ko.applyBindings(viewModel);

        initFullCalendar();
    }
};

ViewModel

// Day constructor that holds the day title and an array of event objects
var Day = function() {
    dayTitle = null,
    events = []
}

function ViewModel() {
    var self = this;

    self.dates = ko.observableArray();
    self.events = ko.observableArray();

    self.uniqueDates = ko.computed(function () {
        var allDates = ko.utils.arrayMap(self.events(), function (event) {
            return new XDate(event.start).toDateString();
        });
        return ko.utils.arrayGetDistinctValues(allDates);
    }, ViewModel);

    // TODO: groupedEvents { date: /.., events: [..]}
    self.groupedEvents = ko.computed(function () {

        var groupedEvents = [];
        var uniqueDates = self.uniqueDates();

        for (var i = 0; i < uniqueDates.length; i++) {
            // create new day object
            var day = new Day();
            day.dayTitle = uniqueDates[i];
            day.events = [];

            // get all events within that day
            ko.utils.arrayForEach(self.events(), function (event) {
                var eventDate = new XDate(event.start).toDateString();
                if (eventDate == uniqueDates[i]) {
                    day.events[day.events.length] = event;
                }
            });
            groupedEvents[groupedEvents.length] = day;
        }
        return groupedEvents;
    });
}

View

<div class="calendar-module-mobile">
    <div class="day-wrapper">
        <p class="day-title">Monday, April 16, 2014</p>   <%--the event startDate--%>
        <div class="mobile-block">
            <ul data-bind="foreach: events">
                <li>
                    <a data-bind="attr: { href: eventUrl }">
                        <p data-bind="text: title"></p>
                        <span class="icon-chevron-right turquoise-icon"></span>
                    </a>
                </li>
            </ul>
        </div>
    </div>
</div>

Update I've updated the ViewModel with some computed observable functions. self.groupedEvents goes through the list of unique dates and returns an array of Day objects, in which each Day object contains the unique dates title and an array of Event objects (not transformed from JSON). I have to now update the View to see if it worked.

1条回答
爷、活的狠高调
2楼-- · 2019-08-03 02:20

Answering my own question here instead of continuing to update the question. Feel free to leave an answer, and if I think it is a better solution, I will give it best answer.

FullCalendar: AJAX call and events

$(fullCalendar).fullCalendar({
    header: {
        left: '',
        center: 'month,agendaWeek,agendaDay',
        right: 'prev,title,next'
    },
    titleFormat: {
        agendaWeek: "(M/DD/YYYY)",
        agendaDay: "dddd MMMM D, YYYY"
    },
    minTime: "07:00:00",
    maxTime: "19:00:00",
    fixedWeekCount: false,
    allDaySlot: false,
    editable: false,
    eventLimit: {
        'month': 3,
        'default': true  // gives default value to other views
    },
    events: {
        url: '/Presentation/test/fullcalendar/GetEvents.ashx',
        type: 'POST',
        success: function (data) {

            // don't render event if not in month (sometimes events at end or beggining of prev/next month are rendered)
            for (var i = 0; i < data.length; i++) {
                if ((new Date(data[i].start).getUTCMonth()) != (new Date($(fullCalendar).fullCalendar('getView').start).getUTCMonth())) {
                    data.splice(i, 1);
                }
            }

            //var parsed = JSON.parse(data);
            viewModel.events(data);     // bind events to KO ViewModel
        }
    },
    /* event handlers */
    eventClick: function(calEvent, jsEvent, view) {

        // hide all overlays initially
        $('.fc-event').find('.overlay').fadeOut();

        var $overlay = $(jsEvent.currentTarget).find('.overlay');
        $overlay.show();

        jsEvent.stopPropagation();
        return false;
    },
    eventRender: function (event, element, view) {    // also fired on loading the eventLimit popup.. so check event

        // if initially loading event on view
        if (event.startDisplayDate == undefined) {
            // format start and end dates
            event.startDisplayDate = new XDate(event.start._i).toString("ddd, MMMM d, h(:mm)tt");
            event.endDisplayDate = new XDate(event.end._i).toString("ddd, MMMM d, h(:mm)tt");
        } else
        {
            // rendering event again, but inside limitclick popover (TODO: find better way)
            event.overlayClasses = "position-left";
        }

        element.append(popupTemplate(event));
    },
    viewRender: function (view, element) {
        $(fullCalendar).fullCalendar('refetchEvents');  // refetch events on view change
    }
});

ViewModel and Objects

var Event = function (event) {
    this.eventID = event.id;
    this.eventTitle = event.title;
    this.startDate = event.start;
    this.startHour = new XDate(event.start).toString("(h:mm)t");
    this.endDate = event.end;
    this.eventUrl = event.eventUrl;
}

// Day constructor that holds the day title and an array of event objects
var Day = function() {
    dayTitle = null,
    events = []
}

function ViewModel() {
    var self = this;

    self.dates = ko.observableArray();
    self.events = ko.observableArray(); // TODO: sort by date

    self.uniqueDates = ko.computed(function () {
        var allDates = ko.utils.arrayMap(self.events(), function (event) {
            return new XDate(event.start).toDateString();
        });
        return ko.utils.arrayGetDistinctValues(allDates);
    }, ViewModel);

    // groupedEvents { date: /.., events: [..]}
    self.groupedEvents = ko.computed(function () {

        var groupedEvents = [];
        var uniqueDates = self.uniqueDates();

        for (var i = 0; i < uniqueDates.length; i++) {
            // create new day object
            var day = new Day();
            day.dayTitle = uniqueDates[i];
            day.events = [];

            // get all events within that day
            ko.utils.arrayForEach(self.events(), function (event) {
                var eventDate = new XDate(event.start).toDateString();
                if (eventDate == uniqueDates[i]) {
                    day.events[day.events.length] = new Event(event);
                }
            });
            groupedEvents[groupedEvents.length] = day;
        }
        return groupedEvents;
    });
}

View

<div class="calendar-module-mobile">
    <div data-bind="foreach: groupedEvents">
        <div class="day-wrapper">
            <p data-bind="text: dayTitle" class="day-title"></p>
            <%--the event startDate--%>
            <div class="mobile-block">
                <ul data-bind="foreach: events">
                    <li>
                        <span data-bind="text: startHour"></span>
                        <a data-bind="attr: { href: eventUrl }">
                            <p data-bind="text: eventTitle"></p>
                            <span class="icon-chevron-right turquoise-icon"></span>
                        </a>
                    </li>
                </ul>
            </div>
        </div>
    </div>
</div>
查看更多
登录 后发表回答