Hello,
There doesn't seem to currently be a year view for the scheduler that would display time in weeks in a timeline, am I correct in this? On the other hand, is there any detailed documentation for implementing your own views. I mean documentation on what functions to overwrite in TimelineMonthView / what they do exactly and what the different timeslots and groups are.
9 Answers, 1 is accepted
An year view as the one you described is not included among the built-in Scheduler views. See the following demo, which demonstrates the available Timeline views.
As for creating a custom view we do not have a tutorial article that explains in detail the functions in a specific view, instead the documentation shows different examples of building custom views:
- Create Custom Views by Inheriting Built-In Views
- Create Custom To-Do Views
- Create Custom Month Views with Event Count in the Show More Button
- Implement Custom Timeline View with Dynamic Length
Regards,
Ivan Danchev
Progress Telerik

Hello,
I have made minor progress and the layout is starting to form as you can see in the attached picture. I haven't quite gotten to grips with the different slots that there are in the scheduler source code, day- and timeslots and the collections that can be formed from either type of slot. Could you give me a pointer on how I would accomplish the goal of having weeks in the scheduler with these slots and still have use of the dragging and rendering capabilities of the scheduler? Should every week be a timeslot and the year a collection of them?

We are glad you've managed to implement the desired view layout.
Regards,
Ivan Danchev
Progress Telerik

Hello Rami
This is exactly what I need to do for a project, would you be able to share the method and implementation please?
It would really help?
Ollie

Sure thing Oliver. Just remember that this solution is cobbled together from reading the Telerik source codes and seems to just about work for what I intend to use it for. And as such, I'm sure it'll break in interesting ways and might even not work at all for you.
Below is the sourcecode for the "weeks in year with grouped resources view". I saved it as WeekYearView.js and then included it in my Index.cshtml. It works (if I remember correctly) by making the "slots" the scheduler uses to be week long and then using those with the default implementation.
var WeeksTimelineGroupedView = kendo.ui.scheduler.TimelineGroupedView.extend({
_addContent: function (dates, columnCount, groupsCount, rowCount, start, end, slotTemplate, isVerticalGrouped) {
// console.log('_addContent');
var view = this._view;
var html = '';
for (var rowIdx = 0; rowIdx <
rowCount
; rowIdx++) {
html += '<tr>';
for (var groupIdx = 0; groupIdx <
groupsCount
; groupIdx++) {
for (
idx
=
0
; idx < dates.length; idx += 7) {
html += '<td> </
td
>';
}
}
html += '</
tr
>';
}
return html;
},
_addTimeSlotsCollections: function (groupCount, tableRows) {
// console.log("_addTimeSlotsCollections");
var view = this._view;
for (var groupIndex = 0; groupIndex <
groupCount
; groupIndex++) {
var
cells
=
tableRows
[groupIndex].children;
var
day
=
view
._startDate;
var
ci
=
0
;
var
collection
=
view
.groups[groupIndex].getTimeSlotCollection(0);
while (day < view._endDate) {
cells[ci].setAttribute('role', 'gridcell');
cells[ci].setAttribute('aria-selected', false);
collection.addTimeSlot(cells[ci], day, kendo.date.addDays(day, 7), true);
day
=
kendo
.date.addDays(day, 7);
ci++;
}
}
},
// don't hide headers
_hideHeaders: function () {
// var
view
=
this
._view;
// view.timesHeader.find('table tr:last').hide();
// view.datesHeader.find('table tr:last').hide();
}
});
// weeknumber function from https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php
// because kendo.date.weekInYear is returning invalid results
function getWeekInfo(d) {
// Copy date so don't modify original
d
=
new
Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
// Set to nearest Thursday: current date + 4 - current day number
// Make Sunday's day number 7
d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
// Get first day of year
var
yearStart
=
new
Date(Date.UTC(d.getUTCFullYear(), 0, 1));
// Calculate full weeks to nearest Thursday
var
weekNo
=
Math
.ceil((((d - yearStart) / 86400000) + 1) / 7);
// Return week number, month and year
return {
week: weekNo,
month: d.getMonth(),
year: d.getUTCFullYear()
};
};
var
WeekYearView
=
kendo
.ui.TimelineView.extend({
_getGroupedView: function () {
// console.log('_getGroupedView');
return new WeeksTimelineGroupedView(this);
},
name: "WeekYearView",
options: {
columnWidth: 35,
currentTimeMarker: false,
height: 700
},
previousDate: function () {
var
s
=
this
.startDate();
return kendo.date.dayOfWeek(kendo.date.addDays(s, -30), 1, -1);
},
// advance by.... one month?
nextDate: function () {
var
s
=
this
.startDate();
return kendo.date.dayOfWeek(kendo.date.addDays(s, 30), 1, -1);
},
// make it one year, from the monday starting with selected date
calculateDateRange: function () {
// console.log('calculateDateRange');
var
selectedDate
=
this
.options.date,
start
=
kendo
.date.dayOfWeek(selectedDate, 1, -1),
end
=
kendo
.date.dayOfWeek(new Date(selectedDate.getFullYear() + 2, selectedDate.getMonth()), 1, 1), dates = [];
//
end
=
kendo
.date.dayOfWeek(new Date(selectedDate.getFullYear(), selectedDate.getMonth() + 4), 1, 1), dates = [];
while (start < end) {
dates.push(start);
start
=
kendo
.date.nextDay(start);
}
this._render(dates);
},
// make proper slots
_calculateSlotRanges: function () {
// console.log('_calculateSlotRanges');
var
dates
=
this
._dates;
var
slotStartTime
=
this
.startTime();
var
slotEndTime
=
this
.endTime();
slotEndTime
=
kendo
.date.getMilliseconds(slotEndTime);
slotStartTime
=
kendo
.date.getMilliseconds(slotStartTime);
if (slotEndTime === slotStartTime) {
slotEndTime += kendo.date.MS_PER_DAY - 1;
} else if (slotEndTime < slotStartTime) {
slotEndTime += kendo.date.MS_PER_DAY;
}
var slotRanges = [];
// week long slots?
for (var
i
=
0
; i < dates.length; i += 7) {
var
rangeStart
=
kendo
.date.getDate(dates[i]);
kendo.date.setTime(rangeStart, slotStartTime);
var
rangeEnd
=
kendo
.date.getDate(dates[i]);
kendo.date.setTime(rangeEnd, slotEndTime);
slotRanges.push({
start: kendo.date.toUtcTime(rangeStart),
end: kendo.date.toUtcTime(rangeEnd)
});
}
this._slotRanges
= slotRanges;
},
_layout: function (dates) {
// console.log('_layout');
var columns = [];
var
that
=
this
;
var rows = [{ text: that.options.messages.defaultRowText }];
var
groupedView
=
that
._groupedView;
/* var
weekNumTemplate
=
kendo
.template('<span class=\'k-link k-nav-day\'>#=kendo.date.weekInYear(date)#</
span
>');
var monthNumTemplate = kendo.template('<
span
class=\'k-link k-nav-day\'>#=kendo.format(\'{0:MMM}\', date)#</
span
>');
var yearNumTemplate = kendo.template('<
span
class=\'k-link k-nav-day\'>#=kendo.format(\'{0:yyyy}\', date)#</
span
>');
var weekNumTemplate = kendo.template('<
span
class=\'k-link k-nav-day\'>#=getWeekInfo(date).week#</
span
>');
var monthNumTemplate = kendo.template('<
span
class=\'k-link k-nav-day\'>#=getWeekInfo(date).month#</
span
>');
var yearNumTemplate = kendo.template('<
span
class=\'k-link k-nav-day\'>#=getWeekInfo(date).year#</
span
>');*/
// loop months over weeks
for (var idx = 0; idx <
dates.length
; idx++) {
var
info
=
getWeekInfo
(dates[idx]);
// has year column already?
for (var
yIdx
=
0
; yIdx < columns.length; yIdx++) {
if (columns[yIdx].yearNum == info.year) break;
}
if (yIdx >= columns.length) {
var yr = {
// text: yearNumTemplate({ date: dates[idx] }),
text: info.year,
className: 'k-slot-cell',
columns: [],
weeks: [],
yearNum: info.year,
colspan: 1
};
columns.push(yr);
yIdx = columns.length - 1;
} else {
columns[yIdx].colspan++;
}
// has month column already?
for (var mIdx = 0; mIdx <
columns
[yIdx].columns.length; mIdx++) {
if (columns[yIdx].columns[mIdx].monthNum == info.month) break;
}
if (mIdx >= columns[yIdx].columns.length) {
var mn = {
//get some localization here please
text: new Date(2018, info.month, 1).toLocaleString("en-us", { month: "short" }),
className: 'k-slot-cell',
columns: [],
monthNum: info.month,
colspan: 1
};
columns[yIdx].columns.push(mn);
mIdx = columns[yIdx].columns.length - 1;
} else {
columns[yIdx].columns[mIdx].colspan++;
}
// has timeslot already?
for (var wIdx = 0; wIdx <
columns
[yIdx].weeks.length; wIdx++) {
if (columns[yIdx].weeks[wIdx] == info.week) break;
}
if (wIdx >= columns[yIdx].weeks.length) {
var ts = {
//text: weekNumTemplate({ date: dates[idx] }),
text: info.week,
className: 'k-slot-cell',
colspan: 1,
weekNum: info.week
}
columns[yIdx].weeks.push(info.week);
columns[yIdx].columns[mIdx].columns.push(ts);
weekColumn = ts;
} else {
weekColumn.colspan++;
}
}
var resources = this.groupedResources;
if (resources.length) {
if (this._groupOrientation() === 'vertical') {
rows = groupedView._createRowsLayout(resources, null, this.groupHeaderTemplate, columns);
columns = groupedView._createVerticalColumnsLayout(resources, null, this.groupHeaderTemplate, columns);
} else {
columns = groupedView._createColumnsLayout(resources, columns, this.groupHeaderTemplate, columns);
}
}
return {
columns: columns,
rows: rows
};
},
_groups: function () {
// console.log('_groups');
var groupCount = this._groupCount();
var dates = this._dates;
this.groups = [];
for (var idx = 0; idx < groupCount; idx++) {
var view = this._addResourceView(idx);
var start = dates[0];
var end = dates[dates.length - 1 || 0];
var startTime = kendo.date.getMilliseconds(this.startTime());
var endTime = kendo.date.getMilliseconds(this.endTime());
if (startTime !== 0 && endTime <= startTime) {
start = kendo.date.getDate(start);
kendo.date.setTime(start, startTime);
end = kendo.date.getDate(end);
kendo.date.setTime(end, endTime);
}
view.addTimeSlotCollection(start, kendo.date.addDays(end, 1));
}
this._timeSlotGroups(groupCount);
},
_timeSlotGroups: function (groupCount) {
// console.log('_timeSlotGroups');
var tableRows = this.content.find('tr');
tableRows.attr('role', 'row');
this._groupedView._addTimeSlotsCollections(groupCount, tableRows);
}
});
And then I use the C# helpers to actually add the Scheduler to my .cshtml with the custom view defined above:
@(Html.Kendo().Scheduler<
SchedulerReservationViewModel
>()
.Name("scheduler")
.Height(900)
.Timezone("Etc/UTC")
.Views(v => {
v.TimelineWeekView(wv => wv.ColumnWidth(40));
v.TimelineWorkWeekView(wwv => wwv.Groups(gr => gr.Date(true)));
v.TimelineMonthView(mv => { mv.ColumnWidth(40); });
v.CustomView("WeekYearView", mv => mv.Selected(true));
})
Oh, and I should mention, I've only used this with grouped resources and with the orientation being vertical: (.Group(gr => gr.Resources("Resource").Orientation(SchedulerGroupOrientation.Vertical))
Hope this helps,
Rami

Oh, and I forgot. The result should be something like this:

Rami,
Thank you so much for this.
This has really saved me about 2 weeks of work and of course works perfectly. If our paths ever cross, ill buy you a beer!
Oliver.

Okay, good to hear. As a last warning, there might be a bug where if the dates (or atleast the startdate?) are not mondays, the grid gets misaligned visually because some slots have fewer days etc.
Rami
Hi Rami,
I have just used this script and works, but i have identity some issues.
- If one week is between to month it will only show for one month. Week no. 48 year 2021
- If i try to edit existing item it will trigger event create
- If i try to create a new item it will trigger event create, but it will show first item in dataSource
- It does not show item in correct week - culture should be "Europe/Copenhagen" and it using this timezone.
do you know how to solve it?
Hello Shaggy,
sorry but I can't give any input about this. I moved to using the vis.js timeline component for the project I was working because it felt better and had easier zoom handling.
Rami