Learn how Collab365 built a large chunk of their conference UI with Kendo UI in this "behind the scenes" post designed for developers.
Collab365 are online event and community providers for SharePoint, Azure and Office 365 who have already delivered seven online conferences. To achieve their goals of getting involved in the community and bringing top quality training to those who need it, they’ve had to keep costs to a minimum, which is why they’ve built their own virtual conference platform.
Collab365 and Kendo UI
This post is part of the Collab365 "Behind the Scenes" series and is aimed at developers who are interested in how they used Kendo UI to build a large chunk of their conference user interface.
Written by Collab365 co-founder Mark Jones, this article was originally published on the Collab365 blog.
We’ve used Kendo UI in lots of places, but this post will concentrate on the following three:
- Conference Agenda and Timeline—built with Kendo UI Scheduler
- Session Room—built using several Kendo UI controls as well as an awesome usage of MVVM
- ‘Which room are people in’ control—built using Kendo UI Chart
A word about our data...
Before we get into how we put the Collab365 Agenda control together, I just wanted to let you in on a secret. When requesting data about our conference (such as speakers, track or session information), we never go directly to our SharePoint lists. You may think this is a strange thing to do, but if you consider that we’re limited by platform size and have to scale to thousands of users (with a burst at any time), we had to think seriously about how we scaled SharePoint.
We also cache as much as possible by using a CDN. This means that JavaScript & CSS files as well as images are all cached outside of SharePoint saving potentially 10,000s of requests per second at peak!
If you use SharePoint for internal needs and have a version above Foundation, you will get the benefits of object caching, page output caching and can easily scale to your maximum estimated load as you know how many staff you have!
Note! As SharePoint lists all support returning data using REST, it’s also very simple to bind them to all of the Kendo UI controls.
Conference Agenda
When running either a physical or virtual conference, one of the key features you need is for attendees to be able to view the agenda before it actually begins (to allow them to build their own agenda), and also during the conference, so they can work out which virtual room to go to.
Agenda Requirements
- We wanted something that could be filtered by parameters such as skill level, track, speaker, audience, etc.
- It needed to render nicely on mobile devices
- The ability for attendees to visualize tracks across a time slot
- Easy to put on both our conference platform and also WordPress site
- Ability to control what’s displayed in the event section
- Something that can work easily with our data, which is in JSON format
How did we build it?
To build the agenda we used the Kendo UI Scheduler control, which is a purpose-built control that allows you to display data in a calendar format. It has two views:
‘Agenda View’
This view is the traditional view that allows you to see what’s happening hour by hour (there are a few more views supported by the scheduler, but we hid them as they weren’t needed).‘Timeline view’
The Timeline view is most useful for our management team as it easily lets us see where we may have conflicts (such as a similar sessions being broadcast at the same time).Databinding
One of the other cool things about using the scheduler is that it supports JSON and the OData protocol out of the box, meaning that all we need to do is give it our list of cached ‘Sessions’ in JSON format. You also tell the control about the relevant metadata and then let it take away all of the pain of rendering the grid.
dataSource: {
filter: mySessionFilters,
data: data,
schema: {
model: {
id: “ID”,
status: function () {
return
getClbSessionStatus(
this
);
},
hashtag: function () {
return
this
.clbSessionHashtag.substring(1);
},
sessionRoomId: function () {
return
this
.clbSessionHashtag.substring(1).replace(‘#’, ‘’);
},
speakerPhoto: function () {
return
tenantUrl +
this
.Session_x0020_Speaker_x003A_Smal;
},
conferenceName: function () {
return
conferenceTitle;
},
tenantUrl: function () {
return
tenantUrl;
},
status: function () {
if
(!
this
.sessionStatus) {
setClbSessionStatus(
this
);
}
return
this
.sessionStatus;
},
statusHtml: function () {
if
(!
this
.sessionStatusHtml) {
setClbSessionStatusHtml(
this
);
}
return
this
.sessionStatusHtml
},
fields: {
taskId: { from: “ID”, type: “number” },
title: { from: “Title”, defaultValue: “No title”, validation: { required:
true
} },
start: { type: “date”, from: “clbSessionStartTime”, parse: function (value) {
if
(value !=
null
)
return
new
Date(parseInt(value.substr(6)));
else
return
conferenceStartDate } },
end: { type: “date”, from: “clbSessionEndTime”, parse: function (value) {
if
(value !=
null
)
return
new
Date(parseInt(value.substr(6)));
else
return
conferenceStartDate } },
level: { from: “clbSessionLevel” },
track: { from: “clbSessionTrack” },
topic: { from: “clbSessionTopic” },
language: { from: “clbSessionLanguage” },
speaker: { from: “clbSessionSpeaker” },
audience: { from: ‘clbSessionSuitableFor’ },
speakerId: { from: ‘Session_x0020_Speaker_x003A_ID’
}
}
}
},
},
}
The “data” as passed in via a “data:data” statement happens to be an array JSON object that defines each session.
initFilters = function(data) {
var filterValues = getFilterValues(data);
container.find(“.levelsFilter”).kendoMultiSelect({
placeholder: “Select skill levels...”,
dataSource: {
data: filterValues.levels
}
});
container.find(“.tracksFilter”).kendoMultiSelect({
placeholder: “Select tracks...”,
dataSource: {
data: filterValues.tracks
}
});
container.find(“.speakersFilter”).kendoMultiSelect({
placeholder: “Select speakers...”,
dataSource: {
data: filterValues.speakers
}
});
container.find(“.audiencesFilter”).kendoMultiSelect({
placeholder: “Select audiences...”,
dataSource: {
data: filterValues.audiences
}
});
container.find(“.topicsFilter”).kendoMultiSelect({
placeholder: “Select topics...”,
dataSource: {
data: filterValues.topics
}
});
container.find(“.statusFilter”).kendoMultiSelect({
placeholder: “Select status...”,
dataSource: {
data: [“Scheduled”, “Live”, “Finished”, “Cancelled”]
}
});
container.find(“.agendaFilters select.agendaFilter”).change(applyFilters);
}
Filters
The filters you see to select subsets of the agenda were also pretty easy to implement once we’d populated the pull down lists (called DropDownLists within Kendo UI) with all of the possible values. The great thing is that, as all of our JSON is downloaded to the browser as a file, we don’t need to go back to the server to filter.
Responsive
17% of our conference attendees access the site via a phone or tablet. That’s nearly 1 in every 5 attendees accessing the pages on a small display. It’s often hard to show lots of data nicely onto a small screen, but with some media queries, the scheduler does a good job.
Portable to WordPress
One other major requirement we have is that we need to publish the agenda long before we open the conference platform. This helps us to tell attendees what sessions are available. However, we didn’t want to duplicate data.
Luckily, as Kendo UI is purely a client-side library (with no server requirements), and also because we don’t go directly to SharePoint lists, it’s very easy to include and reuse it in any web platform.
We built the control to be portable and with very little JavaScript and with a few includes plus some initialization code we were able to get it rendering inside of WordPress with very little effort.
Here’s how it looks on an iPhone 5
Here’s the code to bring it in:
initFilters = function(data) {
var filterValues = getFilterValues(data);
container.find(“.levelsFilter”).kendoMultiSelect({
placeholder: “Select skill levels...”,
dataSource: {
data: filterValues.levels
}
});
container.find(“.tracksFilter”).kendoMultiSelect({
placeholder: “Select tracks...”,
dataSource: {
data: filterValues.tracks
}
});
container.find(“.speakersFilter”).kendoMultiSelect({
placeholder: “Select speakers...”,
dataSource: {
data: filterValues.speakers
}
});
container.find(“.audiencesFilter”).kendoMultiSelect({
placeholder: “Select audiences...”,
dataSource: {
data: filterValues.audiences
}
});
container.find(“.topicsFilter”).kendoMultiSelect({
placeholder: “Select topics...”,
dataSource: {
data: filterValues.topics
}
});
container.find(“.statusFilter”).kendoMultiSelect({
placeholder: “Select status...”,
dataSource: {
data: [“Scheduled”, “Live”, “Finished”, “Cancelled”]
}
});
container.find(“.agendaFilters select.agendaFilter”).change(applyFilters);
}
And here’s how it looks:
The anatomy of a virtual session room
Each session is delivered and presented in its own virtual session room. The session room has the following aspects:
As with nearly all of the platform, it’s very lightweight when accessing SharePoint. We feed in the same browser cached JSON files for session, speaker and sponsor. This means most of the UI rendering is done on the client-side rather than the server. To make our UI as clean and maintainable as possible we take advantage of the Kendo UI in-built MVVM framework. This allows us to bind our data (the “model”) to the page (the “view”) without having to write any of that plumbing code that you normally have to.
Take a look at the code below, you will notice ‘data-bind’ statements which don’t pollute the HTML. Those statements are used by the Kendo UI binding framework and they tell Kendo UI what object to extract from the view and replace at the time that it’s rendered.
<SharePoint:SPSecurityTrimmedControl ID=”SPSecurityTrimmedControlPlayer1” runat=”server” AuthenticationRestrictions=”AnonymousUsersOnly”>
<div
class
=”main-box clearfix”>
<header
class
=”main-box-header clearfix”>
<h2 data-bind=”text: Title”></h2>
<div
class
=”cf”></div>
</header>
<div
class
=”main-box-body clearfix”>
<div id=”playerHeading”>
<div id=”playerTitle” data-bind=”text: clbSubTitle”></div>
<div id=”onAir” data-bind=”style: {color: clbRoomStatusColor}, text: clbRoomStatus”
class
=”offAirStatus”></div>
</div>
<div
class
=”iframe-container” id=”playerContainer”>
<div id=”noMessage”>
<div
class
=”alert alert-block fade
in
” style=”text-align:left;”>
<i
class
=”fa fa-info-circle fa-fw fa-lg” style=”padding-bottom:10px”></i><strong>Not logged In!</strong>
<p>In order to take advantage of the features and also view the sessions, you need to register
for
a free account, or login.</p>
<p>
<asp:literal runat=”server” Text=”<a
class
=’btn btn-primary collreg’ href=’/sitepages/Summit2016.aspx?Source=” />
<SharePoint:ProjectProperty Property=”Url” runat=”server” />
<asp:literal runat=”server” Text=”’ > Register </a>” />
<asp:literal runat=”server” Text=”<a
class
=’btn btn-primary’ href=’/_layouts/15/Authenticate.aspx?Source=” />
<SharePoint:ProjectProperty Property=”Url” runat=”server” />
<asp:literal runat=”server” Text=”’ > Sign In </a>” />
</p>
</div>
</div>
<div id=”playerTools”>
<div id=”clockLogo”>
<img alt=”Collab365”
class
=”playerImage” data-bind=”attr: { src: clbPlayerLogo }” />
<div id=”playerClock”>
</div>
</div>
<div id=”muteBtn”
class
=’plybtn mute’></div>
<div id=”fullScrBrn”
class
=”plybtn fullscreen”></div>
</div>
</div>
</div>
</div>
</SharePoint:SPSecurityTrimmedControl>
The code snippet below illustrates how we create an “observable” object and bind it into “<BODY>” tag in the HTML. The cool thing here is that as the object is ‘observable’ this means that the Kendo UI MVVM framework will keep watching for changes. So if, for example, some user action causes our ObservableRoom to update then the UI also updates automatically. The binding is also two-way meaning that if you had bound “title” to text box, for example, and a user typed in a new title, the backend object would also be updated.
observableRoom = kendo.observable(session);
observableRoom.
set
(“clbTwitterHref”, “https:
//twitter.com/search?q=” + session.clbSessionHashtag);
observableRoom.
set
(“clbTwitterTitle”, “Tweet
using
“ + session.clbSessionHashtag);
observableRoom.
set
(“clbTwitterTxtLink”, “Tweets about "#Collab365 Conference"”);
observableRoom.
set
(“clbDerivedTitle”, session.clbSessionHashtag + “ : “ + session.clbTimeSlot);
observableRoom.
set
(“clbRoomStatus”, “Off Air”);
observableRoom.
set
(“docReadHead”, “<a href=’#’>My Reading Tasks</a>”);
observableRoom.
set
(“docReadStrap”, “Any recommended or required reading tasks that the Speaker, Anchor or Collab365 Team have assigned to you are listed below.”);
observableRoom.
set
(“clbFlagUrl”, siteCollectionUrl + “/Style Library/Conference/Assets/img/flags/32/” + session.clbSessionLanguage + “.png”);
observableRoom.
set
(“clbPlayerLogo”, siteCollectionUrl + “/SiteAssets/sessionlogo.png”);
observableRoom.
set
(“clbProfilePictureUrlLarge”, “/SiteAssets/img_placeholder.png”);
if
(observableRoom.clbSessionYouWillLearn ==
null
) {
observableRoom.clbSessionYouWillLearn = ‘’;
}
observableRoom.clbSessionSuitableFor = clbRoom.cleanSPArray(observableRoom.clbSessionSuitableFor);
observableRoom.clbSessionTopic = clbRoom.cleanSPArray(observableRoom.clbSessionTopic);
kendo.bind($(‘body’), observableRoom);
I am sure you will agree this saves a ton of boilerplate code and reduces the potential for bugs tremendously. If you do any form of complex JavaScript UI, always consider using an approach such as this.
What about AngularJS?
In many cases, you can also easily use AngularJS with Kendo UI. We chose not to as the Kendo UI binding framework was all we needed and didn’t want the extra page weight that Angular brings. However, your case may be different so it’s worth mentioning that you can use it.
Тab Controls
Our session room contains a lot of information and we needed something to organize it without having an endless vertical scroll. So we opted for Kendo UI TabStrip. It’s responsive out of the box and pretty easy to develop.
Here’s an example of our Chat and Tweet tab:
Here’s the mark-up for the Chat and Twitter control:
<div id=”quick_post”>
<div
class
=”main-box clearfix”>
<header
class
=”main-box-header clearfix”>
<h2>Get Social
<span
class
=”chatSponsorContainer”>
sponsored by
<a href=”http:
//beezy.net/” target=”_blank”><img src=”/confs/Summit2016/SiteAssets/beezy-logo-S.png” alt=”Beezy” /></a>
</span>
</h2>
</header>
<div
class
=”main-box-body clearfix”>
<div
class
=”k-widget k-header k-tabstrip” id=”tabstripChat” role=”tablist”>
<ul
class
=”k-tabstrip-items k-reset”>
<li role=”tab”
class
=”k-state-active k-item k-tab-on-top k-state-
default
k-first”>
Chat
</li>
<li role=”tab”>
Tweet
</li>
</ul>
<div
class
=”chatContainer”>
<div id=”chatApp”>
<chat ng-controller=”chatCtrl”></chat>
</div>
</div>
<div>
<div
class
=”twitterContainer”>
<p
class
=”heading”>Join the session conversation on Twitter...
<span
class
=”pull-right twitBtn-container”></span>
</p>
<div
class
=”iframe-container”>
<a data-bind=”attr: { href: clbTwitterHref, data-widget-id: clbTwitterDataId}”
class
=”twitter-timeline” href=”https:
//twitter.com/search?q=%23SPBiz” data-widget-id=”448566281650704385” data-chrome=”transparent noheader”>
View Tweets about
this
session.
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
And here’s the magic that brings the two together:
$(“#tabstripChat”).kendoTabStrip({
activate: on-select,
animation: {
open: {
effects: “fadein”
}
}
});
‘Which room are people in’ control
The third use of Kendo UI that I wanted to bring to your attention is our really popular chart that sits on the home page. This chart comes into its own during conference hours. Its main purpose is to indicate to attendees how many people are in each session. The UI is built using the Kendo UI Chart control and this what it looks like and how we put it together:
The markup is extremely simple, you just need to declare a “DIV” with an ID that can then be referenced in JavaScript.
<div
class
=”main-box”>
<header
class
=”main-box-header clearfix”>
<h2
class
=”pull-left”></>
</header>
<div
class
=”main-box-body clearfix”>
<div id=”UsersByRoomChart”
class
=”cursorPointer”></>
</div>
</div>
Finally, the code that brings it all together is as follows:
$(“#UsersByRoomChart”).kendoChart({
theme: “Bootstrap”,
chartArea: {
height: 250
},
legend: {
visible:
false
},
transitions:
false
,
valueAxis: {
// majorUnit:1,
visible:
false
,
labels: {
visible:
false
},
majorGridLines: {
visible:
false
},
line: {
visible:
false
},
axisCrossingValue: 0
},
seriesDefaults: {
type: “column”
},
series: [{
field: “Connections”,
categoryField: “Speaker”,
color: function(point) {
var colors = [‘#DE3C01’, ‘#076EC4’, ‘#E9D404’, ‘#016E55’, ‘#08428C’, ‘#4F4F4F’, ‘#B6B6B6’];
return
colors[point.index];
}
}],
tooltip: {
visible:
true
,
format: “{0}%”,
font: “1em Segoe UI”,
template: “<img
class
=’sessionSpeakerPhoto hidden-xs’ src=’#= dataItem.SpeakerImage#’> Session: #= dataItem.HubTitle # - #= dataItem.Connections#”
},
seriesClick: func.onSeriesClick,
dataSource: {
data: json
}
Wrap up
I hope you found this article useful. Please don’t hesitate to ask any questions. If you want to know more about Kendo UI, the trial is downloadable here.