Creating Calendar Agenda type widget web parts using Angular – SharePoint 2013

We had to create a web part to show contents from Calendar list in more intuitive way which looks pleasing to eyes and has same design language as whole site. OOTB web part were not enough sufficient, so we decided to go with Angular & REST services of SharePoint 2013.

Here is the final output:

calendar

Note: I saw this free calendar template online and use it as boiler plate to get my result but now I can’t find the link to it. If you find it please let me know, I would love to give credit to the original creator.

Features:

  • Big current month at top
  • Title, Date & Time, location & description in one view.
  • Have navigation arrows to see previous & next month events
  • By default shows current month events and hide the past events of the month
  • Use of modern icons for time & location for visual impact

Steps:

Create a Calendar list in your site and name it CompanyCalendar.

Add powerful moment.js reference to your page layout or masterpage or just for this web part, which ever the scope you are building the solution and going to use it.

Next is to create a javascript file that will do all the logic which will look like:


/*****************************************************************
Company Calendar
******************************************************************/
var angCXCompanyCalendarApp = angular.module('CXCompanyCalendarApp', []);

angCXCompanyCalendarApp.service('CXCompanyCalendarService', ['$http', '$q', function($http, $q) {

    //Get the courses
    this.getItems = function(eventdate) {
        var year = 0;
        var month = 0;
        if (!eventdate) {
            eventdate = moment();
        }
        var date = new Date();

        var dfd = $q.defer();
        var fullUrl = _spPageContextInfo.webAbsoluteUrl + "/_vti_bin/listdata.svc/CompanyCalendar?";
        fullUrl += "$filter=(month(StartTime) eq " + moment(eventdate).format('M') + " and EndTime ge datetime'" + date.toISOString() + "')&$orderby=StartTime";

        $.ajax({
            url: fullUrl,
            type: "GET",
            headers: {
                "accept": "application/json;odata=verbose"
            },
            success: function(data) {
                dfd.resolve(data.d.results);
            },
            error: function(xhr) {
                dfd.reject('Error : (' + xhr.status + ') ' + xhr.statusText + ' Message: ' + xhr.responseJSON.error.message.value);
            }
        });

        return dfd.promise;
    }
}]);

angCXCompanyCalendarApp.controller('CXCompanyCalendarCtrl', function($scope, CXCompanyCalendarService) {

    CXCompanyCalendarService.getItems('').then(
        function(result) {
            var cnow = moment();
            setitems(cnow, result);
        },
        function(reason) {
            //$scope.errMessage = reason;
        });

    $scope.getitem = function(value, calendarDate) {
        var cnow = moment(calendarDate);

        if (value == '0') {
            cnow = cnow.subtract(1, 'month');
        } else {
            cnow = cnow.add(1, 'month');
        }

        CXCompanyCalendarService.getItems(cnow).then(
            function(result) {
                setitems(cnow, result);
            },
            function(reason) {
                //$scope.errMessage = reason;
            });

    }

    function setitems(cnow, result) {
        $scope.calender = [];
        $scope.calender.date = cnow;
        $scope.calender.name = moment(cnow).format("MMMM YYYY");

        $scope.calender.events = [];

        angular.forEach(result, function(event) {
            var eTime = '';
            var sDate = moment(event.StartTime).add(5, 'h');
            var eDate = moment(event.EndTime).add(5, 'h');
            if (event.AllDayEvent) {
                eTime = 'All day event';
            } else {
                eTime = moment(sDate).format("HH:mm") + ' - ' + moment(eDate).format("HH:mm");
            }

            var newEvent = {
                eventday: moment(event.StartTime).format("DD"),
                endday: moment(event.EndTime).format("DD"),
                month: moment(event.StartTime).format("MMM"),
                title: event.Title,
                location: event.Location,
                eventtime: eTime,
                description: event.Description
            }
            $scope.calender.events.push(newEvent);
        });
    }
});

In this code, simple logic is applied. I’m getting today date & month and based on that getting events of this month starting from current date. On click on next or previous buttons, adding or subtracting 1 from from current month and hitting the server to get new values.

Now the code along with html we need to add in script editor web part to render this output, will look like this:

<script>
    var calendarListTitle = 'Social Calendar';
</script>
<div ng-controller="CXCompanyCalendarCtrl" class="cx-events">
    <div id="cx-events">
        <div class="cx-nav-container">
            <h2>{{calender.name}}</h2>
            <i class="fa fa-chevron-circle-left cx-arrow-left" ng-click="getitem('0',calender.date)"></i><i class="fa fa-chevron-circle-right cx-arrow-right" ng-click="getitem('1',calender.date)"></i>
        </div>
        <div class="cx-events-container">
            <ul>
                <li class="cx-event" ng-repeat="event in calender.events">
                    <div class="cx-date" ng-switch on="event">
                        <div class="cx-day">{{event.eventday}}</div>
                        <div class="cx-day_end" ng-class="{'cx-hide' : event.eventday === event.endday}"> - {{event.endday}}</div>
                        <div class="cx-month">{{event.month}}</div>
                    </div>
                    <div class="cx-content">
                        <ul>
                            <li class="cx-name">
                                <h4>{{event.title}}</h4>
                            </li>
                            <li class="cx-time"><i class="fa fa-clock-o"></i> {{event.eventtime}} <i class="fa fa-location-arrow"></i> {{event.location}}</li>
                            <li class="cx-description">
                                {{event.description}}
                            </li>
                        </ul>
                    </div>
                </li>
                <li class="cx-event" ng-if="calender.events.length < 1">
                    <div class="cx-noevents">
                        No events available.
                    </div>
                </li>
            </ul>
        </div>
    </div>
</div>

Last step is to add CSS, which looks like.

/* #cx-events default styles
================================================== */
.cx-hide {
    display: none;
}

#cx-events h1, #cx-events h2, #cx-events h3, #cx-events h4, #cx-events h5, #cx-events h6 {
    color: #666666;
}

#cx-events h2 {
    display: inline;
    font-weight: lighter;
    margin: 0;
    padding: 0;
    padding-right: 10px;
}

.cx-nav-arrows {
    display: inline;
}

.cx-nav-container i {
    display: inline;
    font-size: 24px;
    padding: 2px;
    color: #666666;
    line-height: inherit;
}

.cx-nav-container i:hover {
    cursor: pointer;
}

.cx-event {
    border: 2px solid #ebebeb;
    border-bottom: 0px;
    overflow: hidden;
}

.cx-events-container &amp;gt; ul &amp;gt; li:last-child {
    border-bottom: 2px solid #ebebeb;
}

.cx-events-container ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
}

.cx-date {
    float: left;
    width: 70px;
    height: 90px;
    color: #FFF;
    background: #666666;
    padding-left: 5px;
    padding-right: 5px;
}

.cx-content {
    overflow: hidden;
    height: 90px;
}

.cx-day {
    font-size: 20px;
    float: left;
    width: 30px;
    padding-top: 10px;
}

.cx-day_end {
    font-size: 14px;
    padding-top: 10px;
}

.cx-month {
    font-size: 24px;
    left: 5px;
    bottom: 5px;
    width: 30px;
    text-transform: uppercase;
}

.cx-name h4 {
    font-weight: 600;
}

.cx-content {
    padding-top: 5px;
    padding-left: 5px;
}

.cx-noevents {
    font-size: 14px;
    height: 80px;
    padding: 15px;
}

.cx-event:hover {
    background: #f2f3f1; /* Old browsers */
    background: -moz-linear-gradient(top, #f2f3f1 0%, #f4f4f4 58%, #fafafa 100%); /* FF3.6+ */
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f2f3f1), color-stop(58%,#f4f4f4), color-stop(100%,#fafafa)); /* Chrome,Safari4+ */
    background: -webkit-linear-gradient(top, #f2f3f1 0%,#f4f4f4 58%,#fafafa 100%); /* Chrome10+,Safari5.1+ */
    background: -o-linear-gradient(top, #f2f3f1 0%,#f4f4f4 58%,#fafafa 100%); /* Opera 11.10+ */
    background: -ms-linear-gradient(top, #f2f3f1 0%,#f4f4f4 58%,#fafafa 100%); /* IE10+ */
    background: linear-gradient(to bottom, #f2f3f1 0%,#f4f4f4 58%,#fafafa 100%); /* W3C */
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f2f3f1', endColorstr='#fafafa',GradientType=0 ); /* IE6-9 */
}
/* ================================================== */

And that’s it, you will now have responsive calendar agenda widget.

Known limitation: Currently this code won’t handle repeating events properly, will only show it first occurrence. It is due to how SharePoint saves the reoccurring events.

More Widgets like this to follow soon.

6 thoughts on “Creating Calendar Agenda type widget web parts using Angular – SharePoint 2013

  1. Hi,

    I tried implementing this calendar widget, but can’t get it to work. Do you have a link to download the files? The Javascript part seems to have a lot of extra code that doesn’t seem to belong there for example the &quot;

    Thanks,
    AJ

Leave a Reply

Your email address will not be published. Required fields are marked *