@progress/kendo-ui
Version:
This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.
1,298 lines (1,297 loc) • 155 kB
JavaScript
//#region ../src/kendo.scheduler.js
const __meta__ = {
id: "scheduler",
name: "Scheduler",
category: "web",
description: "The Scheduler is an event calendar.",
depends: [
"dropdownlist",
"editable",
"multiselect",
"window",
"datepicker",
"datetimepicker",
"toolbar",
"scheduler.recurrence",
"scheduler.view",
"html.button",
"icons"
],
features: [
{
id: "scheduler-dayview",
name: "Scheduler Day View",
description: "Scheduler Day View",
depends: ["scheduler.dayview"]
},
{
id: "scheduler-agendaview",
name: "Scheduler Agenda View",
description: "Scheduler Agenda View",
depends: ["scheduler.agendaview"]
},
{
id: "scheduler-monthview",
name: "Scheduler Month View",
description: "Scheduler Month View",
depends: ["scheduler.monthview"]
},
{
id: "scheduler-timelineview",
name: "Scheduler Timeline View",
description: "Scheduler Timeline View",
depends: ["scheduler.timelineview"]
},
{
id: "scheduler-yearview",
name: "Scheduler Year View",
description: "Scheduler Year View",
depends: ["scheduler.yearview"]
},
{
id: "scheduler-mobile",
name: "Scheduler adaptive rendering",
description: "Support for adaptive rendering",
depends: [
"dialog",
"pane",
"switch"
]
},
{
id: "scheduler-pdf-export",
name: "PDF export",
description: "Export the scheduler events as PDF",
depends: ["pdf", "drawing"]
},
{
id: "scheduler-timezones",
name: "Timezones",
description: "Allow selecting timezones different than Etc/UTC",
depends: ["timezones"]
}
]
};
(function($, undefined) {
var kendo = window.kendo, date = kendo.date, MS_PER_DAY = date.MS_PER_DAY, getDate = date.getDate, getMilliseconds = kendo.date.getMilliseconds, recurrence = kendo.recurrence, encode = kendo.htmlEncode, keys = kendo.keys, ui = kendo.ui, Widget = ui.Widget, DataBoundWidget = ui.DataBoundWidget, STRING = "string", Popup = ui.Popup, Calendar = ui.Calendar, DataSource = kendo.data.DataSource, isPlainObject = $.isPlainObject, extend = $.extend, toString = Object.prototype.toString, isArray = Array.isArray, NS = ".kendoScheduler", CLICK = "click", MOUSEDOWN = "mousedown", TOUCHSTART = kendo.support.pointers ? "pointerdown" : "touchstart", TOUCHMOVE = kendo.support.pointers ? "pointermove" : "touchmove", TOUCHEND = kendo.support.pointers ? "pointerup" : "touchend", MOUSEMOVE = kendo.support.mousemove, CHANGE = "change", PROGRESS = "progress", ERROR = "error", CANCEL = "cancel", REMOVE = "remove", RESET = "resetSeries", SAVE = "save", ADD = "add", EDIT = "edit", DISABLED = "disabled", OPTION = "option", FOCUSEDSTATE = "k-focus", INVERSECOLORCLASS = "k-event-inverse", valueStartEndBoundRegex = /(?:value:start|value:end)(?:,|$)/, MIN_SCREEN = "(min-width: 1024px)", TODAY = getDate(new Date()), EXCEPTION_SEPARATOR = ",", OLD_EXCEPTION_SEPARATOR_REGEXP = /\;/g, RECURRENCE_EXCEPTION = "recurrenceException", DELETECONFIRM = "Are you sure you want to delete this event?", DELETERECURRING = "Do you want to delete only this event occurrence or the whole series?", EDITRECURRING = "Do you want to edit only this event occurrence or the whole series?", DELETERECURRINGCONFIRM = "Are you sure you want to delete this event occurrence?", RESETSERIESCONFIRM = "Are you sure you want to reset the whole series?", DELETESERIESCONFIRM = "Are you sure you want to delete the whole series?", ONGOING_CLASS = "k-event-ongoing", COMMANDBUTTONTMPL = ({ className, attr, text, icon, fillMode, themeColor }) => kendo.html.renderButton(`<button type="button" class="${className}" ${attr}>${text}</button>`, {
icon,
fillMode,
themeColor
}), VIEWS_DROPDOWN_TEMPLATE = kendo.template(({ label, views, type }) => `<select aria-label="${label}" class="k-picker k-dropdown-list k-dropdown ${type}">` + Object.keys(views).map((view) => `<option value="${view}">${views[view].title}</option>`).join("") + "</select>"), DEFAULT_TOOLS = {
pdf: {
name: "pdf",
type: "button",
icon: "file-pdf",
attributes: { class: "k-pdf" }
},
pdfMobile: {
name: "pdf",
type: "button",
icon: "file-pdf",
showText: "overflow",
attributes: { class: "k-pdf" }
},
today: {
name: "today",
type: "button",
attributes: { "ref-nav-today": "" }
},
previous: {
name: "previous",
type: "button",
icon: "caret-alt-left",
showText: "overflow",
attributes: { "ref-nav-prev": "" },
groupClass: "k-scheduler-navigation"
},
next: {
name: "next",
type: "button",
icon: "caret-alt-right",
showText: "overflow",
attributes: { "ref-nav-next": "" },
groupClass: "k-scheduler-navigation"
},
current: {
name: "current",
type: "button",
icon: "calendar",
fillMode: "flat",
text: "placeholder",
attributes: {
"aria-live": "polite",
class: "k-nav-current"
}
},
search: { template: "<span class=\"k-scheduler-search k-textbox k-input\">" + "<input tabindex=\"-1\" autocomplete=\"off\" class=\"k-input-inner k-scheduler-search-input k-input-inner\" />" + `<span class="k-input-suffix">${kendo.ui.icon("search")}</span>` + "</span>" },
refresh: {
name: "refresh",
type: "button",
icon: "arrow-rotate-cw",
showText: "overflow",
attributes: { class: "k-scheduler-refresh" }
},
create: {
name: "create",
type: "button",
icon: "plus",
attributes: { class: "k-create-event" }
},
calendar: {
name: "calendar",
type: "button",
icon: "calendar",
attributes: { class: "k-nav-calendar" }
},
previousMobile: {
name: "previous",
type: "button",
icon: "chevron-left",
showText: "overflow",
attributes: { "ref-nav-prev": "" },
groupClass: "k-scheduler-navigation"
},
nextMobile: {
name: "next",
type: "button",
icon: "chevron-right",
showText: "overflow",
attributes: { "ref-nav-next": "" },
groupClass: "k-scheduler-navigation"
},
currentMobile: { template: "<span class=\"k-scheduler-navigation\">" + "<span class=\"k-nav-current\">" + "<span class=\"k-m-date-format\"></span>" + "<span class=\"k-y-date-format\"></span>" + "</span>" + "</span>" },
view: {
name: "view",
type: "button",
togglable: true,
group: "views"
}
}, defaultDesktopTools = [
[
"today",
"previous",
"next"
],
"current",
{ type: "spacer" }
], defaultMobileToolsFirst = [["calendar"], { type: "spacer" }], defaultMobileToolsSecond = [
"previousMobile",
{ type: "spacer" },
"currentMobile",
{ type: "spacer" },
"nextMobile"
], MOBILEDATERANGEEDITOR = function(container, options) {
var attr = {
name: options.field,
title: options.title
};
var isAllDay = options.model.isAllDay;
var dateTimeValidate = kendo.attr("validate") + "='" + !isAllDay + "'";
var dateValidate = kendo.attr("validate") + "='" + !!isAllDay + "'";
appendTimezoneAttr(attr, options);
appendValidDateValidator(attr, options);
appendDateCompareValidator(attr, options);
$("<input type=\"datetime-local\" required " + kendo.attr("type") + "=\"datetime-local\" " + kendo.attr("bind") + "=\"value:" + options.field + ", invisible:isAllDay\" " + dateTimeValidate + "/>").attr(attr).appendTo(container);
$("<input type=\"date\" required " + kendo.attr("type") + "=\"date\" " + kendo.attr("bind") + "=\"value:" + options.field + ",visible:isAllDay\" " + dateValidate + "/>").attr(attr).appendTo(container);
$("<span " + kendo.attr("for") + "=\"" + options.field + "\" class=\"k-invalid-msg\"/>").hide().appendTo(container);
}, DATERANGEEDITOR = function(container, options) {
var attr = {
name: options.field,
title: options.title
}, isAllDay = options.model.isAllDay, dateTimeValidate = kendo.attr("validate") + "='" + !isAllDay + "' ", dateValidate = kendo.attr("validate") + "='" + !!isAllDay + "' ";
appendTimezoneAttr(attr, options);
appendValidDateValidator(attr, options);
appendDateCompareValidator(attr, options);
$("<input type=\"text\" required " + kendo.attr("type") + "=\"date\"" + " " + kendo.attr("role") + "=\"datetimepicker\" " + kendo.attr("bind") + "=\"value:" + options.field + ",invisible:isAllDay\" " + dateTimeValidate + "/>").attr(attr).appendTo(container);
$("<input type=\"text\" required " + kendo.attr("type") + "=\"date\"" + " " + kendo.attr("role") + "=\"datepicker\" " + kendo.attr("bind") + "=\"value:" + options.field + ",visible:isAllDay\" " + dateValidate + "/>").attr(attr).appendTo(container);
$("<span " + kendo.attr("bind") + "=\"text: " + options.field + "Timezone\"></span>").appendTo(container);
if (options.field === "end") {
$("<span " + kendo.attr("bind") + "=\"text: startTimezone, invisible: endTimezone\"></span>").appendTo(container);
}
$("<span " + kendo.attr("for") + "=\"" + options.field + "\" class=\"k-invalid-msg\"/>").hide().appendTo(container);
}, RECURRENCEEDITOR = function(container, options) {
$("<div " + kendo.attr("bind") + "=\"value:" + options.field + "\" />").attr({ name: options.field }).appendTo(container).kendoRecurrenceEditor({
start: options.model.start,
timezone: options.timezone,
messages: options.messages
});
}, MOBILERECURRENCEEDITOR = function(container, options) {
$("<div " + kendo.attr("bind") + "=\"value:" + options.field + "\" />").attr({ name: options.field }).appendTo(container).kendoMobileRecurrenceEditor({
start: options.model.start,
timezone: options.timezone,
messages: options.messages,
pane: options.pane,
value: options.model[options.field]
});
}, MOBILEISALLDAYEDITOR = function(container, options) {
$("<input type=\"checkbox\" data-role=\"switch\"" + kendo.attr("bind") + "=\"value:" + options.field + "\" />").appendTo(container);
}, ISALLDAYEDITOR = function(container, options) {
$("<input type=\"checkbox\" data-role=\"checkbox\"" + kendo.attr("bind") + "=\"value:" + options.field + "\" data-label=\"" + options.title + "\" />").attr({
id: options.field,
name: options.field,
title: options.title ? options.title : options.field
}).appendTo(container);
}, MOBILETIMEZONEPOPUP = function(container, options) {
var text = timezoneButtonText(options.model, options.messages.noTimezone);
$("<span class=\"k-timezone-label\"></span>").text(text).appendTo(container);
$(kendo.ui.icon("arrow-chevron-right")).appendTo(container);
container.closest("li.k-item label").on(CLICK, options.click);
}, TIMEZONEPOPUP = function(container, options) {
$("<a href=\"#\" class=\"k-button\" data-bind=\"invisible:isAllDay\"><span class=\"k-button-text\">" + options.messages.timezoneEditorButton + "</span></a>").on(CLICK, options.click).appendTo(container);
}, MOBILETIMEZONEEDITOR = function(container, options) {
$("<div class=\"k-mobiletimezoneeditor\" " + kendo.attr("bind") + "=\"value:" + options.field + "\" />").attr({ name: options.field }).appendTo(container).kendoMobileTimezoneEditor({ optionLabel: options.noTimezone });
}, TIMEZONEEDITOR = function(container, options) {
var visible = options.visible || options.visible === undefined;
$("<div " + kendo.attr("bind") + "=\"value:" + options.field + "\" />").attr({ name: options.field }).toggle(visible).appendTo(container).kendoTimezoneEditor({
optionLabel: options.noTimezone,
title: options.title
});
};
function timezoneButtonText(model, message) {
message = message || "";
if (model.startTimezone) {
message = model.startTimezone;
if (model.endTimezone) {
message += " | " + model.endTimezone;
}
}
return message;
}
function appendTimezoneAttr(attrs, options) {
var timezone = options.timezone;
if (timezone) {
attrs[kendo.attr("timezone")] = timezone;
}
}
function appendValidDateValidator(attrs, options) {
var validationRules = options.model.fields[options.field].validation;
if (validationRules) {
var validDateRule = validationRules.validDateValidator;
if (validDateRule && isPlainObject(validDateRule) && validDateRule.message) {
attrs[kendo.attr("validDate-msg")] = validDateRule.message;
}
}
}
function appendDateCompareValidator(attrs, options) {
var validationRules = options.model.fields[options.field].validation;
if (validationRules) {
var dateCompareRule = validationRules.dateCompare;
if (dateCompareRule && isPlainObject(dateCompareRule) && dateCompareRule.message) {
attrs[kendo.attr("dateCompare-msg")] = dateCompareRule.message;
}
}
}
function wrapDataAccess(originalFunction, timezone) {
return function(data) {
data = originalFunction(data);
convertData(data, "apply", timezone);
return data || [];
};
}
function wrapDataSerialization(originalFunction, timezone) {
return function(data) {
if (data) {
if (toString.call(data) !== "[object Array]" && !(data instanceof kendo.data.ObservableArray)) {
data = [data];
}
}
convertData(data, "remove", timezone, true);
data = originalFunction(data);
return data || [];
};
}
function convertData(data, method, timezone, removeUid) {
var event, idx, length, startOffset, endOffset;
data = data || [];
for (idx = 0, length = data.length; idx < length; idx++) {
event = data[idx];
startOffset = event.start ? event.start.getTimezoneOffset() : null;
endOffset = event.start ? event.end.getTimezoneOffset() : null;
if (removeUid) {
if (event.startTimezone || event.endTimezone) {
if (timezone) {
event.start = kendo.timezone.convert(event.start, event.startTimezone || event.endTimezone, timezone);
event.end = kendo.timezone.convert(event.end, event.endTimezone || event.startTimezone, timezone);
event.start = kendo.timezone[method](event.start, timezone);
event.end = kendo.timezone[method](event.end, timezone);
} else {
event.start = kendo.timezone[method](event.start, event.startTimezone || event.endTimezone);
event.end = kendo.timezone[method](event.end, event.endTimezone || event.startTimezone);
}
} else if (timezone) {
event.start = kendo.timezone[method](event.start, timezone);
event.end = kendo.timezone[method](event.end, timezone);
}
} else {
if (event.startTimezone || event.endTimezone) {
event.start = kendo.timezone[method](event.start, event.startTimezone || event.endTimezone);
event.end = kendo.timezone[method](event.end, event.endTimezone || event.startTimezone);
if (timezone) {
event.start = kendo.timezone.convert(event.start, event.startTimezone || event.endTimezone, timezone);
event.end = kendo.timezone.convert(event.end, event.endTimezone || event.startTimezone, timezone);
}
} else if (timezone) {
event.start = kendo.timezone[method](event.start, timezone);
event.end = kendo.timezone[method](event.end, timezone);
}
}
if (removeUid) {
delete event.uid;
}
if (method === "remove" && event.start && startOffset && startOffset !== event.start.getTimezoneOffset()) {
event.start = new Date(event.start.getTime() + (startOffset - event.start.getTimezoneOffset()) * 6e4);
}
if (method === "remove" && event.end && endOffset && endOffset !== event.end.getTimezoneOffset()) {
event.end = new Date(event.end.getTime() + (endOffset - event.end.getTimezoneOffset()) * 6e4);
}
}
return data;
}
function getOccurrenceByUid(data, uid) {
var length = data.length, idx = 0, event;
for (; idx < length; idx++) {
event = data[idx];
if (event.uid === uid) {
return event;
}
}
}
var SchedulerDataReader = kendo.Class.extend({
init: function(schema, reader) {
var timezone = schema.timezone;
this.reader = reader;
if (reader.model) {
this.model = reader.model;
}
this.timezone = timezone;
this.data = wrapDataAccess(this.data.bind(this), timezone);
this.serialize = wrapDataSerialization(this.serialize.bind(this), timezone);
},
errors: function(data) {
return this.reader.errors(data);
},
parse: function(data) {
return this.reader.parse(data);
},
data: function(data) {
return this.reader.data(data);
},
total: function(data) {
return this.reader.total(data);
},
groups: function(data) {
return this.reader.groups(data);
},
aggregates: function(data) {
return this.reader.aggregates(data);
},
serialize: function(data) {
return this.reader.serialize(data);
}
});
function applyZone(date, fromZone, toZone) {
if (toZone) {
date = kendo.timezone.convert(date, fromZone, toZone);
} else {
date = kendo.timezone.remove(date, fromZone);
}
return date;
}
function validDateValidator(input) {
if (input.filter("[name=start]").length && input.filter("[title=Start]").length || input.filter("[name=end]").length && input.filter("[title=End]").length || input.filter(".k-recur-until").length) {
var date;
var picker = kendo.widgetInstance(input, kendo.ui);
if (picker) {
date = kendo.parseDate(input.val(), picker.options.format);
return !!date && picker.value();
} else {
date = kendo.parseDate(input.val());
return !!date;
}
}
return true;
}
function dateCompareValidator(input) {
if (input.filter("[name=end]").length) {
var container = input.closest(".k-scheduler-edit-form");
var startInput = container.find("[name=start]:visible");
var endInput = container.find("[name=end]:visible");
if (endInput[0] && startInput[0]) {
var start, end;
var startPicker = kendo.widgetInstance(startInput, kendo.ui);
var endPicker = kendo.widgetInstance(endInput, kendo.ui);
var editable = container.data("kendoEditable");
var model = editable ? editable.options.model : null;
if (startPicker && endPicker) {
start = startPicker.value();
end = endPicker.value();
} else {
start = kendo.parseDate(startInput.val());
end = kendo.parseDate(endInput.val());
}
if (start && end) {
if (model) {
var timezone = startInput.attr(kendo.attr("timezone"));
var startTimezone = model.startTimezone;
var endTimezone = model.endTimezone;
startTimezone = startTimezone || endTimezone;
endTimezone = endTimezone || startTimezone;
if (startTimezone) {
start = applyZone(start, startTimezone, timezone);
end = applyZone(end, endTimezone, timezone);
}
}
return start <= end;
}
}
}
return true;
}
function untilDateCompareValidator(input) {
var untilPicker, until, container, startInput, start, startPicker;
if (input.filter(".k-recur-until").length) {
untilPicker = kendo.widgetInstance(input, kendo.ui);
until = untilPicker.value();
container = input.closest(".k-scheduler-edit-form");
startInput = container.find("[name=start]:visible");
if (startInput[0]) {
startPicker = kendo.widgetInstance(startInput, kendo.ui);
if (startPicker) {
start = startPicker.value();
} else {
start = kendo.parseDate(startInput.val());
}
if (start && until) {
return start <= until;
}
}
}
return true;
}
var SchedulerEvent = kendo.data.Model.define({
init: function(value) {
var that = this;
kendo.data.Model.fn.init.call(that, value);
that._defaultId = that.defaults[that.idField];
},
_time: function(field) {
var date = this[field];
var fieldTime = "_" + field + "Time";
if (this[fieldTime]) {
return this[fieldTime] - kendo.date.toUtcTime(kendo.date.getDate(date));
}
return getMilliseconds(date);
},
_date: function(field) {
var fieldTime = "_" + field + "Time";
if (this[fieldTime]) {
return this[fieldTime] - this._time(field);
}
return kendo.date.getDate(this[field]);
},
clone: function(options, updateUid) {
var uid = this.uid, event = new this.constructor($.extend({}, this.toJSON(), options));
if (!updateUid) {
event.uid = uid;
}
return event;
},
duration: function() {
var end = this.end;
var start = this.start;
var offset = (end.getTimezoneOffset() - start.getTimezoneOffset()) * kendo.date.MS_PER_MINUTE;
return end - start - offset;
},
expand: function(start, end, zone) {
return recurrence ? recurrence.expand(this, start, end, zone) : [this];
},
update: function(eventInfo) {
for (var field in eventInfo) {
this.set(field, eventInfo[field]);
}
if (this._startTime) {
this.set("_startTime", kendo.date.toUtcTime(this.start));
}
if (this._endTime) {
this.set("_endTime", kendo.date.toUtcTime(this.end));
}
},
isMultiDay: function() {
return this.isAllDay || this.duration() >= kendo.date.MS_PER_DAY;
},
isException: function() {
return !this.isNew() && this.recurrenceId;
},
isOccurrence: function() {
return this.isNew() && this.recurrenceId;
},
isRecurring: function() {
return !!(this.recurrenceRule || this.recurrenceId);
},
isRecurrenceHead: function() {
return !!(this.id && this.recurrenceRule);
},
toOccurrence: function(options) {
options = $.extend(options, {
recurrenceException: null,
recurrenceRule: null,
recurrenceId: this.id || this.recurrenceId
});
options[this.idField] = this.defaults[this.idField];
return this.clone(options, true);
},
toJSON: function() {
var obj = kendo.data.Model.fn.toJSON.call(this);
obj.uid = this.uid;
delete obj._startTime;
delete obj._endTime;
return obj;
},
shouldSerialize: function(field) {
return kendo.data.Model.fn.shouldSerialize.call(this, field) && field !== "_defaultId";
},
set: function(key, value) {
var isAllDay = this.isAllDay || false;
kendo.data.Model.fn.set.call(this, key, value);
if (key == "isAllDay" && value != isAllDay) {
var start = kendo.date.getDate(this.start);
var end = new Date(this.end);
var milliseconds = kendo.date.getMilliseconds(end);
if (milliseconds === 0 && value) {
milliseconds = MS_PER_DAY;
}
this.set("start", start);
if (value === true) {
kendo.date.setTime(end, -milliseconds);
if (end < start) {
end = start;
}
} else {
kendo.date.setTime(end, MS_PER_DAY - milliseconds);
}
this.set("end", end);
}
},
id: "id",
fields: {
id: { type: "number" },
title: {
defaultValue: "",
type: "string"
},
start: {
type: "date",
validation: {
required: true,
validDate: { value: validDateValidator }
}
},
startTimezone: { type: "string" },
end: {
type: "date",
validation: {
required: true,
validDate: { value: validDateValidator },
dateCompare: { value: dateCompareValidator }
}
},
endTimezone: { type: "string" },
recurrenceRule: {
defaultValue: "",
type: "string",
validation: {
validDate: { value: validDateValidator },
untilDateCompare: { value: untilDateCompareValidator }
}
},
recurrenceException: {
defaultValue: "",
type: "string"
},
isAllDay: {
type: "boolean",
defaultValue: false
},
description: { type: "string" }
}
});
var SchedulerDataSource = DataSource.extend({
init: function(options) {
DataSource.fn.init.call(this, extend(true, {}, { schema: {
modelBase: SchedulerEvent,
model: SchedulerEvent
} }, options));
this.reader = new SchedulerDataReader(this.options.schema, this.reader);
},
expand: function(start, end) {
var data = this.view(), filter = {}, endOffset;
if (start && end) {
endOffset = end.getTimezoneOffset();
end = new Date(end.getTime() + MS_PER_DAY - 1);
if (end.getTimezoneOffset() !== endOffset) {
end = kendo.timezone.apply(end, endOffset);
}
filter = {
logic: "or",
filters: [{
logic: "and",
filters: [
{
field: "start",
operator: "gte",
value: start
},
{
field: "end",
operator: "gte",
value: start
},
{
field: "start",
operator: "lte",
value: end
}
]
}, {
logic: "and",
filters: [{
field: "start",
operator: "lte",
value: new Date(start.getTime() + MS_PER_DAY - 1)
}, {
field: "end",
operator: "gte",
value: start
}]
}]
};
data = new kendo.data.Query(expandAll(data, start, end, this.reader.timezone)).filter(filter).toArray();
}
return data;
},
cancelChanges: function(model) {
if (model && model.isOccurrence()) {
this._removeExceptionDate(model);
}
DataSource.fn.cancelChanges.call(this, model);
},
insert: function(index, model) {
if (!model) {
return;
}
if (!(model instanceof SchedulerEvent)) {
var eventInfo = model;
model = this._createNewModel();
model.accept(eventInfo);
}
if (!this._pushCreated && model.isRecurrenceHead() || model.recurrenceId) {
model = model.recurrenceId ? model : model.toOccurrence();
this._addExceptionDate(model);
}
return DataSource.fn.insert.call(this, index, model);
},
pushCreate: function(items) {
this._pushCreated = true;
DataSource.fn.pushCreate.call(this, items);
this._pushCreated = false;
},
remove: function(model) {
if (model.isRecurrenceHead()) {
this._removeExceptions(model);
} else if (model.isRecurring()) {
this._addExceptionDate(model);
}
return DataSource.fn.remove.call(this, model);
},
_removeExceptions: function(model) {
var data = this.data().slice(0), item = data.shift(), id = model.id;
while (item) {
if (item.recurrenceId === id) {
DataSource.fn.remove.call(this, item);
}
item = data.shift();
}
model.set(RECURRENCE_EXCEPTION, "");
},
_removeExceptionDate: function(model) {
if (model.recurrenceId) {
var head = this.get(model.recurrenceId);
if (head) {
var start = model.defaults.start;
var replaceRegExp = new RegExp("(\\" + EXCEPTION_SEPARATOR + "?)" + recurrence.toExceptionString(start, this.reader.timezone));
var recurrenceException = (head.recurrenceException || "").replace(OLD_EXCEPTION_SEPARATOR_REGEXP, EXCEPTION_SEPARATOR).replace(/\,$/, "");
if (replaceRegExp.test(recurrenceException)) {
head.set(RECURRENCE_EXCEPTION, recurrenceException.replace(replaceRegExp, ""));
} else {
start = model.start;
replaceRegExp = new RegExp("(\\" + EXCEPTION_SEPARATOR + "?)" + recurrence.toExceptionString(start, this.reader.timezone));
head.set(RECURRENCE_EXCEPTION, recurrenceException.replace(replaceRegExp, ""));
}
}
}
},
_addExceptionDate: function(model) {
var start = model.start;
var zone = this.reader.timezone;
var head = this.get(model.recurrenceId);
var recurrenceException = (head.recurrenceException || "").replace(OLD_EXCEPTION_SEPARATOR_REGEXP, EXCEPTION_SEPARATOR).replace(/\,$/, "");
if (!recurrence.isException(recurrenceException, start, zone)) {
var newException = recurrence.toExceptionString(start, zone);
model.defaults.start = start;
head.set(RECURRENCE_EXCEPTION, recurrenceException + (recurrenceException && newException ? EXCEPTION_SEPARATOR : "") + newException);
}
}
});
function expandAll(events, start, end, zone) {
var length = events.length, data = [], idx = 0;
for (; idx < length; idx++) {
data = data.concat(events[idx].expand(start, end, zone));
}
return data;
}
SchedulerDataSource.create = function(options) {
if (isArray(options) || options instanceof kendo.data.ObservableArray) {
options = { data: options };
}
var dataSource = options || {}, data = dataSource.data;
dataSource.data = data;
if (!(dataSource instanceof SchedulerDataSource) && dataSource instanceof kendo.data.DataSource) {
throw new Error("Incorrect DataSource type. Only SchedulerDataSource instances are supported");
}
return dataSource instanceof SchedulerDataSource ? dataSource : new SchedulerDataSource(dataSource);
};
extend(true, kendo.data, {
SchedulerDataSource,
SchedulerDataReader,
SchedulerEvent
});
var defaultCommands = {
update: {
text: "Save",
className: "k-button-primary k-scheduler-update"
},
canceledit: {
text: "Cancel",
className: "k-scheduler-cancel"
},
destroy: {
text: "Delete",
icon: "trash",
imageClass: "k-i-trash",
className: "k-button-primary k-scheduler-delete",
iconClass: "k-icon"
}
};
function trimOptions(options, overrideOptions) {
delete options.name;
delete options.prefix;
delete options.remove;
delete options.edit;
delete options.add;
delete options.navigate;
for (var key in overrideOptions) {
options[key] = overrideOptions[key];
}
return options;
}
function descriptionEditor(options) {
var attr = createValidationAttributes(options.model, options.field);
return function(container, model) {
$("<textarea name=\"description\" class=\"k-input-inner\" title=\"" + model.title + "\"/>").attr(attr).appendTo(container).wrap("<span class=\"k-input k-textarea\"></span>");
};
}
function createValidationAttributes(model, field) {
var modelField = (model.fields || model)[field];
var specialRules = [
"url",
"email",
"number",
"date",
"boolean"
];
var validation = modelField ? modelField.validation : {};
var datatype = kendo.attr("type");
var inArray = $.inArray;
var ruleName;
var rule;
var attr = {};
for (ruleName in validation) {
rule = validation[ruleName];
if (inArray(ruleName, specialRules) >= 0) {
attr[datatype] = ruleName;
} else if (!kendo.isFunction(rule)) {
attr[ruleName] = isPlainObject(rule) ? rule.value || ruleName : rule;
}
attr[kendo.attr(ruleName + "-msg")] = rule.message;
}
return attr;
}
function filterResourceEditorData(editor, parentValue, parentValueField, valueField) {
var editorValue = editor.value(), isMs = Array.isArray(editorValue), valueArray;
if (isMs) {
valueArray = JSON.parse(JSON.stringify(editorValue));
} else {
valueArray = [editorValue.toString()];
}
editor.dataSource.data().forEach(function(item) {
if (item[parentValueField] === null || item[parentValueField] === undefined || item[parentValueField] == parentValue) {
item.set(DISABLED, false);
} else {
var currentValue = item.get(valueField);
item.set(DISABLED, true);
if (valueArray.indexOf(currentValue) >= 0 || valueArray.indexOf(currentValue.toString()) >= 0) {
if (isMs) {
valueArray.splice(valueArray.indexOf(currentValue), 1);
} else {
editor.value(null);
editor.trigger(CHANGE);
}
}
}
});
if (isMs && valueArray.length < editorValue.length) {
editor.value(valueArray);
editor.trigger(CHANGE);
}
}
function bindParentValueChangeHandler(container, currentEditor, resource, parent) {
var parentElement = container.closest(".k-edit-form-container").find("[data-" + kendo.ns + "bind='value:" + parent + "']");
var parentWidget = parentElement.getKendoDropDownList();
if (parentWidget) {
parentWidget.bind(CHANGE, function(ev) {
var parentValue = ev.sender.value();
filterResourceEditorData(currentEditor, parentValue, resource.dataParentValueField, resource.dataValueField);
});
} else {
parentElement.on(CHANGE, function(ev) {
var parentValue = ev.target.value;
filterResourceEditorData(currentEditor, parentValue, resource.dataParentValueField, resource.dataValueField);
});
}
}
function filterMobileResourceEditorData(resource, currentEditor, parentSelectedValue) {
var options = currentEditor.find(OPTION), editorValue = currentEditor.val(), isMs = Array.isArray(editorValue), valueArray;
if (isMs) {
valueArray = JSON.parse(JSON.stringify(editorValue));
} else {
valueArray = [editorValue];
}
resource.dataSource.view().forEach(function(item, index) {
var itemParentValue = kendo.getter(resource.dataParentValueField)(item);
var valid = itemParentValue === null || itemParentValue === undefined || itemParentValue == parentSelectedValue;
if (valid) {
options[index].removeAttribute(DISABLED);
} else {
options[index].setAttribute(DISABLED, DISABLED);
var currentValue = "" + item.get(resource.dataValueField);
if (valueArray.indexOf(currentValue) >= 0) {
if (isMs) {
valueArray.splice(valueArray.indexOf(currentValue), 1);
} else {
currentEditor.val(null);
currentEditor.trigger(CHANGE);
}
}
}
});
if (isMs && valueArray.length < editorValue.length) {
currentEditor.val(valueArray);
currentEditor.trigger(CHANGE);
}
}
function dropDownResourceEditor(resource, model, parent) {
var attr = createValidationAttributes(model, resource.field);
return function(container) {
var currentEditor;
if (parent) {
setTimeout(function() {
filterResourceEditorData(currentEditor, model[parent], resource.dataParentValueField, resource.dataValueField);
bindParentValueChangeHandler(container, currentEditor, resource, parent);
});
}
currentEditor = $(kendo.format("<select aria-labelledby=\"" + resource.field + "_label\" data-{0}bind=\"value:{1}\" title=\"" + model.title + "\">", kendo.ns, resource.field)).appendTo(container).attr(attr).kendoDropDownList({
dataTextField: resource.dataTextField,
dataValueField: resource.dataValueField,
dataSource: resource.dataSource.data(),
valuePrimitive: resource.valuePrimitive,
optionLabel: "None",
template: (data) => `<span ${data.disabled ? "data-disabled" : ""}><span class="k-scheduler-mark" ${kendo.attr("style-background-color")}="${data[resource.dataColorField] || "none"}"></span>${data[resource.dataTextField]}</span>`,
select: function(e) {
if (e.dataItem && e.dataItem.disabled) {
e.preventDefault();
}
},
dataBound: function(e) {
let options = e.sender.list.find("li");
for (let i = 0; i < options.length; i++) {
var element = options.eq(i);
if (element.find("[data-disabled]").length > 0) {
element.addClass("k-disabled");
}
}
}
}).data("kendoDropDownList");
};
}
function dropDownResourceEditorMobile(resource, model, parent) {
var attr = createValidationAttributes(model, resource.field);
return function(container) {
var options = "";
var view = resource.dataSource.view();
for (var idx = 0, length = view.length; idx < length; idx++) {
options += kendo.format("<option value=\"{0}\">{1}</option>", kendo.getter(resource.dataValueField)(view[idx]), kendo.getter(resource.dataTextField)(view[idx]));
}
var currentEditor = $(kendo.format("<select aria-labelledby=\"" + resource.field + "_label\" data-{0}bind=\"value:{1}\">{2}</select>", kendo.ns, resource.field, options)).appendTo(container).attr(attr);
if (parent) {
setTimeout(function() {
var parentElement = container.closest(".k-stretched-view").find("[data-" + kendo.ns + "bind='value:" + parent + "']");
var parentSelectedValue = model[parent];
filterMobileResourceEditorData(resource, currentEditor, parentSelectedValue);
parentElement.on(CHANGE, function(ev) {
var parentValue = ev.target.value;
filterMobileResourceEditorData(resource, currentEditor, parentValue);
});
});
}
};
}
function multiSelectResourceEditor(resource, model, parent) {
var attr = createValidationAttributes(model, resource.field);
return function(container) {
var currentEditor;
if (parent) {
setTimeout(function() {
filterResourceEditorData(currentEditor, model[parent], resource.dataParentValueField, resource.dataValueField);
bindParentValueChangeHandler(container, currentEditor, resource, parent);
});
}
currentEditor = $(kendo.format("<select aria-labelledby=\"" + resource.field + "_label\" data-{0}bind=\"value:{1}\">", kendo.ns, resource.field)).appendTo(container).attr(attr).kendoMultiSelect({
dataTextField: resource.dataTextField,
dataValueField: resource.dataValueField,
dataSource: resource.dataSource.data(),
valuePrimitive: resource.valuePrimitive,
itemTemplate: (data) => `<span ${data.disabled ? "data-disabled" : ""}><span class="k-scheduler-mark" ${kendo.attr("style-background-color")}="${data[resource.dataColorField] || "none"}"></span>${data[resource.dataTextField]}</span>`,
tagTemplate: (data) => `<span class="k-scheduler-mark" ${kendo.attr("style-background-color")}="${data[resource.dataColorField] || "none"}"></span>${data[resource.dataTextField]}`,
select: function(e) {
if (e.dataItem && e.dataItem.disabled) {
e.preventDefault();
}
},
dataBound: function(e) {
let options = e.sender.list.find("li");
for (let i = 0; i < options.length; i++) {
let element = options.eq(i);
if (element.find("[data-disabled]").length > 0) {
element.addClass("k-disabled");
}
}
}
}).data("kendoMultiSelect");
};
}
function multiSelectResourceEditorMobile(resource, model, parent) {
var attr = createValidationAttributes(model, resource.field);
return function(container) {
var options = "";
var view = resource.dataSource.view();
for (var idx = 0, length = view.length; idx < length; idx++) {
options += kendo.format("<option value=\"{0}\">{1}</option>", kendo.getter(resource.dataValueField)(view[idx]), kendo.getter(resource.dataTextField)(view[idx]));
}
var currentEditor = $(kendo.format("<select aria-labelledby=\"" + resource.field + "_label\" data-{0}bind=\"value:{1}\" multiple=\"multiple\">{2}</select>", kendo.ns, resource.field, options)).appendTo(container).attr(attr);
if (parent) {
setTimeout(function() {
var parentElement = container.closest(".k-stretched-view").find("[data-" + kendo.ns + "bind='value:" + parent + "']");
var parentSelectedValue = model[parent];
filterMobileResourceEditorData(resource, currentEditor, parentSelectedValue);
parentElement.on(CHANGE, function(ev) {
var parentValue = ev.target.value;
filterMobileResourceEditorData(resource, currentEditor, parentValue);
});
});
}
};
}
function moveEventRange(event, distance) {
var duration = event.end.getTime() - event.start.getTime();
var start = new Date(event.start.getTime());
kendo.date.setTime(start, distance);
var end = new Date(start.getTime());
kendo.date.setTime(end, duration, true);
return {
start,
end
};
}
var editors = {
mobile: {
dateRange: MOBILEDATERANGEEDITOR,
timezonePopUp: MOBILETIMEZONEPOPUP,
timezone: MOBILETIMEZONEEDITOR,
recurrence: MOBILERECURRENCEEDITOR,
description: descriptionEditor,
multipleResources: multiSelectResourceEditorMobile,
resources: dropDownResourceEditorMobile,
isAllDay: MOBILEISALLDAYEDITOR
},
desktop: {
dateRange: DATERANGEEDITOR,
timezonePopUp: TIMEZONEPOPUP,
timezone: TIMEZONEEDITOR,
recurrence: RECURRENCEEDITOR,
description: descriptionEditor,
multipleResources: multiSelectResourceEditor,
resources: dropDownResourceEditor,
isAllDay: ISALLDAYEDITOR
}
};
var Editor = kendo.Observable.extend({
init: function(element, options) {
kendo.Observable.fn.init.call(this);
this.element = element;
this.options = extend(true, {}, this.options, options);
this.createButton = this.options.createButton;
this.toggleDateValidationHandler = this._toggleDateValidation.bind(this);
},
_toggleDateValidation: function(e) {
if (e.field == "isAllDay") {
var container = this.container, isAllDay = this.editable.options.model.isAllDay, bindAttribute = kendo.attr("bind"), element, isDateTimeInput, shouldValidate;
container.find("[" + bindAttribute + "*=end],[" + bindAttribute + "*=start]").each(function() {
element = $(this);
if (valueStartEndBoundRegex.test(element.attr(bindAttribute))) {
isDateTimeInput = element.is("[" + kendo.attr("role") + "=datetimepicker],[type*=datetime]");
shouldValidate = isAllDay !== isDateTimeInput;
element.attr(kendo.attr("validate"), shouldValidate.toString());
}
});
}
},
fields: function(editors, model) {
var that = this;
var messages = that.options.messages;
var timezone = that.options.timezone;
var click = function(e) {
e.preventDefault();
that._initTimezoneEditor(model, this);
};
var fields = [
{
field: "title",
title: messages.editor.title
},
{
field: "start",
title: messages.editor.start,
editor: editors.dateRange,
timezone
},
{
field: "end",
title: messages.editor.end,
editor: editors.dateRange,
timezone
},
{
field: "isAllDay",
title: messages.editor.allDayEvent,
editor: editors.isAllDay
}
];
var checkHierarchical = function(item) {
return !!item[resource.dataParentValueField];
};
if (kendo.timezone.windows_zones) {
fields.push({
field: "timezone",
title: messages.editor.timezone,
editor: editors.timezonePopUp,
click,
messages: messages.editor,
model
});
fields.push({
field: "startTimezone",
title: messages.editor.startTimezone,
editor: editors.timezone,
noTimezone: messages.editor.noTimezone
});
fields.push({
field: "endTimezone",
title: messages.editor.endTimezone,
editor: editors.timezone,
noTimezone: messages.editor.noTimezone
});
}
if (!model.recurrenceId) {
fields.push({
field: "recurrenceRule",
title: messages.editor.repeat,
editor: editors.recurrence,
timezone,
messages: messages.recurrenceEditor,
pane: this.pane
});
}
if ("description" in model) {
fields.push({
field: "description",
title: messages.editor.description,
editor: editors.description({
model,
field: "description"
})
});
}
for (var resourceIndex = 0; resourceIndex < this.options.resources.length; resourceIndex++) {
var resource = this.options.resources[resourceIndex];
var resourceView = resource.dataSource.view();
var hasParent = resourceView.some(checkHierarchical);
var parentResource, parent;
if (hasParent) {
parentResource = this.options.resources[resourceIndex - 1];
if (parentResource) {
parent = parentResource.field;
}
}
fields.push({
field: resource.field,
title: resource.title,
editor: resource.multiple ? editors.multipleResources(resource, model, parent) : editors.resources(resource, model, parent)
});
}
return fields;
},
end: function() {
return this.editable.end();
},
_buildDesktopEditTemplate: function(model, fields, editableFields) {
var messages = this.options.messages;
const startTimezone = `<div class="k-popup-edit-form k-scheduler-edit-form k-scheduler-timezones" ${kendo.attr("style-display")}="none">` + "<div class=\"k-form\">" + "<div class=\"k-form-field\"><div class=\"k-form-field-wrap\">" + kendo.html.renderCheckBox($("<input class=\"k-timezone-toggle\"/>"), { label: messages.editor.separateTimezones }) + "</div></div>";
const editableField = (fieldName) => {
const isEditable = !model.editable || model.editable(fieldName);
if (isEditable) {
return `<div ${kendo.attr("container-for")}="${fieldName}" class="k-form-field-wrap"></div>`;
} else {
return `<div class="k-form-field-wrap">${fieldName && kendo.getter(fieldName)(model) || ""}</div>`;
}
};
const generateFields = (field) => {
const fieldName = field.field;
const fieldTitle = field.title;
const modelField = model.fields[fieldName];
const isEditable = !model.editable || model.editable(fieldName);
if (isEditable) {
editableFields.push(field);
}
return (fieldName === "startTimezone" ? startTimezone : "") + (modelField && modelField.type === "boolean" ? `<div class="k-form-field">${editableField(fieldName)}</div>` : `<div class="k-form-field"><label class="k-label k-form-label" for="${fieldName}" id="${fieldName}_label">${fieldTitle || fieldName || ""}</label>${editableField(fieldName)}</div>`) + (fieldName === "endTimezone" ? this._createEndTimezoneButton() : "");
};
return `<div class="k-form">` + fields.map(generateFields).join("") + `</div>`;
},
_buildMobileEditTemplate: function(model, fields, editableFields) {
var messages = this.options.messages;
const startTimezone = `<div class="k-popup-edit-form k-scheduler-edit-form k-scheduler-timezones" ${kendo.attr("style-display")}="none">` + "<ul class=\"k-listgroup k-listgroup-flush\">" + "<li class=\"k-item k-listgroup-item\">" + "<label class=\"k-label k-listgroup-form-row\">" + "<span class=\"k-item-title k-listgroup-form-row\">" + encode(messages.editor.separateTimezones) + "</span>" + "<span class=\"k-listgroup-form-field-wrapper\">" + "<input class=\"k-timezone-toggle\" data-role=\"switch\" type=\"checkbox\" />" + "</span>" + "</label>" + "</li>";
const editableField = (field) => {
const fieldName = field.field;
const fieldTitle = field.title;
const isEditable = !model.editable || model.editable(fieldName);
let midPart;
if (isEditable) {
midPart = `${fieldName === "timezone" ? `<label class="k-label k-listgroup-form-row" data-bind="css: { k-disabled: isAllDay }">` : `<label class="k-label k-listgroup-form-row">`}` + `<span class="k-item-title k-listgroup-form-field-label">${fieldTitle || fieldName || ""}</span>` + `<div class="k-listgroup-form-field-wrapper" ${kendo.attr("container-for")}="${fieldName}"></div>`;
} else {
midPart = `<li class="k-item k-listgroup-item">` + `<label class="k-label k-no-click k-listgroup-form-row">` + `<span class="k-item-title k-listgroup-form-field-label">${fieldTitle || fieldName || ""}</span>` + `<span class="k-no-editor k-listgroup-form-field-wrapper">${fieldName && kendo.getter(fieldName)(model) || ""}</span>`;
}
return `<li class="k-item k-listgroup-item">` + midPart + `</label></li>`;
};
const generateFields = (field) => {
const fieldName = field.field;
const isEditable = !model.editable || model.editable(fieldName);
if (isEditable) {
editableFields.push(field);
}
return (fieldName === "timezone" || fieldName === "recurrenceRule" ? "</ul><ul class=\"k-listgroup k-listgroup-flush\">" : "") + (fieldName === "startTimezone" ? startTimezone : "") + editableField(field) + (fieldName === "recurrenceRule" ? "</ul><ul class=\"k-listgroup k-listgroup-flush\">" : "") + (fieldName === "endTimezone" ? "</ul></div>" : "");
};
const result = `<ul class="k-listgroup k-listgroup-flush">` + fields.map(generateFields).join("") + `</ul>`;
return result;
},
_buildEditTemplate: function(model, fields, editableFields, isMobile) {
var settings = extend({}, kendo.Template, this.options.templateSettings);
var template = this.options.editable.template;
var html = "";
if (template) {
if (typeof template === STRING) {
template = kendo.unescape(template);
}
html += kendo.template(template, settings)(model);
} else if (isMobile) {
html += "<div data-role=\"content\">" + this._buildMobileEditTemplate(model, fields, editableFields) + "</div>";
} else {
html += this._buildDesktopEditTemplate(model, fields, editableFields);
}
return html;
},
_createEndTimezoneButton: function() {
return "</ul></div>";
},
_revertTimezones: function(model) {
model.set("startTimezone", this._startTimezone);
model.set("endTimezone", this._endTimezone);
delete this._startTimezone;
delete this._endTimezone;
}
});
var MobileEditor = Editor.extend({
init: function() {
Editor.fn.init.apply(this, arguments);
this.pane = kendo.Pane.wrap(this.element, { viewEngine: { viewOptions: {
renderOnInit: true,
wrap: false,
wrapInSections: true,
detachOnHide: false,
detachOnDestroy: false
} } });
this.pane.element.parent().css("height", this.options.height);
this.view = this.pane.view();
},
options: { animations: {
left: "slide",
right: "slide:right"
} },
destroy: function() {
this.close();
this.unbind();
this.pane.destroy();
},
_initTimezoneEditor: function(model) {
var that = this;
var pane = that.pane;
var messages = that.options.messages;
var timezoneView = that.timezoneView;
var container = timezoneView ? timezoneView.content.find(".k-scheduler-timezones") : that.container.find(".k-scheduler-timezones");
var kSwitch = container.find("input.k-timezone-toggle").data("kendoSwitch");
var endTimezoneRow = container.find("li.k-item:not(.k-zonepicker)").last();
var startTimezoneChange = function(e) {
if (e.field === "startTimezone") {
var value = model.startTimezone;
kSwitch.enable(value);
if (!value) {
endTimezoneRow.hide();
model.set("endTimezone", "");
kSwitch.value(false);
}
}
};
that._startTimezone = model.startTimezone || "";
that._endTimezone = model.endTimezone || "";
if (!timezoneView) {
var html = "<div data-role=\"view\" class=\"k-popup-edit-form k-scheduler-edit-form\">" + "<div data-role=\"header\" class=\"k-appbar k-appbar-primary\">" + kendo.html.renderButton(`<button class="k-header-cancel k-scheduler-cancel" title="${messages.cancel}" aria-label="${messages.cancel}"></button>`, {
fillMode: "flat",
icon: "chevron-left"
}) + "<span class=\"k-spacer\"></span>" + `<span>${encode(messages.editor.timezoneTitle)}</span>` + "<span class=\"k-spacer\"></span>" + encode(messages.editor.timezoneTitle) + kendo.html.renderButton(`<button class="k-header-done k-scheduler-update" title="${messages.save}" aria-label="${messages.save}"></button>`, {
fillMode: "flat",
icon: "check"
}) + "</div><div data-role=\"content\"></div>";
this.timezoneView = timezoneView = pane.append(html);
timezoneView.contentElement.append(container.show());
timezoneView.element.on(CLICK + NS, ".k-scheduler-cancel, .k-scheduler-update", function(e) {
e.preventDefault();
e.stopPropagation();
if ($(this).hasClass("k-sched