table-schedule
Version:
Vanilla JS table schedule with DnD support.
1,789 lines (1,502 loc) • 56.8 kB
JavaScript
/**
* TableSchedule.js v0.4.0 by Monkey-D-Pixel
* git@github.com:Monkey-D-Pixel/table-schedule.git
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.TableSchedule = factory());
}(this, function () { 'use strict';
function _typeof(obj) {
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
var createElem = function createElem(obj) {
if (!obj || !obj.tagName) {
return null;
}
var el = document.createElement(obj.tagName);
if (obj.attr && Object.keys(obj.attr).length) {
for (var key in obj.attr) {
if (obj.attr[key] !== null && obj.attr[key] !== undefined) {
el.setAttribute(key, String(obj.attr[key]));
}
}
}
if (obj.id && typeof obj.id === 'string') {
el.id = obj.id;
}
if (obj.className && typeof obj.className === 'string') {
el.className = obj.className;
}
if (obj.style && _typeof(obj.style) === 'object' && Object.keys(obj.style).length) {
Object.assign(el.style, obj.style);
}
var c = obj.children;
if (c && c instanceof Array && c.length) {
for (var i = 0; i < c.length; i++) {
var child = createElem(c[i]);
if (child) {
el.appendChild(child);
}
}
} else if (c !== undefined && c !== null) {
if (obj.html) {
el.innerHTML = String(c);
} else {
el.innerText = String(c);
}
}
return el;
};
var zeroize = function zeroize(n) {
if (n < 10) {
return '0' + n;
} else {
return n.toString();
}
};
var fullDate = function fullDate(date) {
return date.getFullYear() + '/' + zeroize(date.getMonth() + 1) + '/' + zeroize(date.getDate());
};
var parseDate = function parseDate(s) {
s = (s || '').replace(/-/g, '/');
var parsed = Date.parse(s);
return new Date(parsed);
};
var hhmm = function hhmm(date) {
return zeroize(date.getHours()) + ':' + zeroize(date.getMinutes());
};
var minuteToTimeStr = function minuteToTimeStr(minute) {
var h = parseInt(minute / 60),
m = parseInt(minute % 60);
if (h < 10) h = '0' + h;
if (m < 10) m = '0' + m;
return h + ':' + m;
};
var timeStrToMinute = function timeStrToMinute(s) {
s = s || '';
var arr = s.split(':');
var h = parseInt(arr[0]);
var m = parseInt(arr[1]);
if (!isNaN(h) && !isNaN(m)) {
return h * 60 + m;
} else {
return 0;
}
};
var closest = function closest(el, selector) {
var ans = el.parentElement;
while (ans && !ans.matches(selector)) {
ans = ans.parentElement;
}
return ans;
};
var getDelegate = function getDelegate(e, selector) {
var el = e.target;
while (el && !el.matches(selector)) {
if (el === e.currentTarget) {
el = null;
break;
}
el = el.parentElement;
}
return el;
}; // https://github.com/olahol/scrollparent.js/blob/master/scrollparent.js
var regex = /(auto|scroll)/;
var style = function style(node, prop) {
return getComputedStyle(node, null).getPropertyValue(prop);
};
var overflow = function overflow(node) {
return style(node, "overflow") + style(node, "overflow-y") + style(node, "overflow-x");
};
var scroll = function scroll(node) {
return regex.test(overflow(node));
};
var findScrollParent = function findScrollParent(el) {
var ans = el.parentElement;
while (ans && !scroll(ans)) {
ans = ans.parentElement;
}
return ans || document.scrollingElement || document.documentElement;
};
var getFixRect = function getFixRect(el) {
return el.getBoundingClientRect();
};
var getRect = function getRect(el, scroll) {
scroll = scroll || findScrollParent(el);
var rect = el.getBoundingClientRect().toJSON();
rect.top += scroll.scrollTop;
rect.bottom += scroll.scrollTop;
rect.left += scroll.scrollLeft;
rect.right += scroll.scrollLeft;
return rect;
};
var getOffset = function getOffset(el, parent) {
parent = parent || el.parentElement;
var rect1 = getFixRect(el);
var rect2 = getFixRect(parent);
return {
top: rect1.top - rect2.top,
left: rect1.left - rect2.left,
width: rect1.width,
height: rect1.height
};
};
var dispatchEvent = function dispatchEvent(element, type, data) {
var event; // Event and CustomEvent on IE9-11 are global objects, not constructors
if (typeof Event === 'function' && typeof CustomEvent === 'function') {
event = new CustomEvent(type, {
detail: data,
bubbles: true,
cancelable: true
});
} else {
event = document.createEvent('CustomEvent');
event.initCustomEvent(type, true, true, data);
}
return element.dispatchEvent(event);
};
var hasProp = function hasProp(obj, p) {
return Object.prototype.hasOwnProperty.call(obj, p);
};
var includesAny = function includesAny(arr1, arr2) {
if (!Array.isArray(arr1) || !arr1.length) {
return false;
}
var pool = [];
if (Array.isArray(arr2)) {
pool = arr2;
} else {
pool.push(arr2);
}
for (var i = 0; i < pool.length; i++) {
if (arr1.indexOf(pool[i]) > -1) {
return true;
}
}
return false;
};
var DEFAULTS = {
startDate: new Date(),
num: 7,
dayStart: 6,
// hour
dayEnd: 22,
gap: 10,
// minute
quantizing: true,
// use quantization without(true) or with(false) SHIFT key
createThreshold: 10,
// minute, used on touchmove & touchend
stretchThreshold: 5,
// minute, used on touchmove
moveYThreshold: 5,
// minute, used on touchmove
dateFormat: function dateFormat(date) {
return fullDate(date).substring(5);
},
directChange: false,
// if true, addEvent/updateEvent will be automatically called; or array of wanted ones
extraDataset: null,
// add extra dataset entries to event element, e.g. {id: 'ID'} will add 'data-id' with value from eventItem.ID
labelGroups: false,
// whether show group header
groupHeaderText: null // default using item.group | a key from item object | a function which takes item.group
};
var CELL_HEIGHT = 20; // px
var MOVE_X_THRESHOLD = 40; // px
var EVENT = {
create: 'create',
modify: 'modify',
remove: 'remove'
};
var TOUCH_DELAY = 500; // ms
var timeoutRef = null;
var privates = {
_genDates: function _genDates(update) {
this.dates = [];
var ite = new Date(this.config.startDate);
for (var i = 0; i < this.config.num; i++) {
this.dates.push(new Date(ite));
if (!update) {
this.events.push([]);
this._events.push([]);
}
ite.setDate(ite.getDate() + 1);
}
},
_genThead: function _genThead() {
var c = this.config;
var dates = this.dates;
var thead = {
tagName: 'THEAD',
props: {},
children: [{
tagName: 'TR',
children: [{
tagName: 'TH'
}]
}]
};
for (var i = 0; i < dates.length; i++) {
thead.children[0].children.push({
tagName: 'TH',
children: [{
tagName: 'div',
children: c.dateFormat(dates[i]),
html: true
}, {
tagName: 'table',
className: 'group-header',
children: [{
tagName: 'thead',
children: [{
tagName: 'tr'
}]
}]
}]
});
}
return thead;
},
_renderOuter: function _renderOuter() {
var c = this.config;
var dates = this.dates; // thead for dates
var thead = this._genThead(); // left column
var timeline = {
tagName: 'TABLE',
className: 'time-line',
children: [{
tagName: 'TBODY',
children: []
}]
};
var da = new Date();
da.setHours(c.dayStart, 0, 0, 0);
var rows = Math.ceil((c.dayEnd - c.dayStart) * 60 / c.gap);
for (var i = 0; i < rows; i++) {
timeline.children[0].children.push({
tagName: 'TR',
children: [{
tagName: 'TH',
children: [{
tagName: 'div',
children: hhmm(da)
}]
}]
});
da.setMinutes(da.getMinutes() + c.gap);
} // tbody
var tbody = {
tagName: 'TBODY',
children: [{
tagName: 'TR',
children: [{
tagName: 'TD',
className: 'time-line-wrapper',
children: [timeline]
}]
}]
};
for (var j = 0; j < dates.length; j++) {
tbody.children[0].children.push({
tagName: 'TD',
className: 'day-col'
});
}
this.el.root.appendChild(createElem(thead));
this.el.root.appendChild(createElem(tbody));
},
_setGrid: function _setGrid(index, cols) {
cols = cols || 1;
var c = this.config;
var rows = Math.ceil((c.dayEnd - c.dayStart) * 60 / c.gap);
var grid = this.el.dayGrids[index].querySelector('tbody');
var r = {
tagName: 'TR',
children: []
};
for (var i = 0; i < cols; i++) {
r.children.push({
tagName: 'TD',
className: 'grid-cell'
});
}
grid.innerHTML = '';
for (var _i = 0; _i < rows; _i++) {
grid.appendChild(createElem(r));
}
},
_renderInner: function _renderInner(index) {
var dayCols = this.el.dayCols;
var h = {
tagName: 'DIV',
className: 'events-container',
attr: {
'data-index': 0
},
children: [{
tagName: 'DIV',
className: 'events-col'
}]
};
var g = {
tagName: 'TABLE',
className: 'day-grid',
children: [{
tagName: 'TBODY',
children: []
}]
};
if (typeof index === 'number' && dayCols[index]) {
dayCols[index].innerHTML = '';
dayCols[index].appendChild(createElem(g));
this._setGrid(index);
h.attr['data-index'] = index;
dayCols[index].appendChild(createElem(h));
} else {
for (var i = 0; i < dayCols.length; i++) {
dayCols[i].innerHTML = '';
dayCols[i].appendChild(createElem(g));
this._setGrid(i);
h.attr['data-index'] = i;
dayCols[i].appendChild(createElem(h));
}
}
},
_renderEvent: function _renderEvent(item) {
var c = this.config;
var index = item.dateIndex;
var top = (item.startm - c.dayStart * 60) / c.gap * CELL_HEIGHT;
var h = (item.endm - item.startm) / c.gap * CELL_HEIGHT;
var style = item.style && _typeof(item.style) === 'object' ? item.style : {};
var className = item.className ? String(item.className).trim() : '';
var attr = {
'data-startm': item.startm,
'data-endm': item.endm,
'data-nr': item.nr
};
if (c.extraDataset && Object.keys(c.extraDataset).length) {
for (var key in c.extraDataset) {
if (!hasProp(attr, 'data-' + key) && hasProp(item, c.extraDataset[key])) {
attr['data-' + key] = item[c.extraDataset[key]];
}
}
}
var elem = createElem({
tagName: 'DIV',
className: 'event' + (className ? ' ' + className : ''),
style: Object.assign({}, style, {
top: top + 'px',
height: h + 'px'
}),
attr: attr,
children: [{
tagName: 'DIV',
className: 'event-content-wrapper',
children: [{
tagName: 'DIV',
className: 'event-title',
children: item.title || ''
}, {
tagName: 'DIV',
className: 'event-content',
children: item.content,
html: true
}]
}, {
tagName: 'DIV',
className: 'event-bar-wrapper',
children: [{
tagName: 'SPAN',
className: 'event-bar'
}]
}]
});
this._insertIntoDate(elem, index);
},
_insertIntoDate: function _insertIntoDate(elem, index) {
// get event-cols; try to insert into one of them; if fails, add one col
var allCon = this.el.eventsContainers;
var cols = allCon[index].children;
var inserted = false;
var nr = elem.dataset['nr'];
var item = this.events[index][nr];
for (var i = 0; i < cols.length; i++) {
inserted = this._insertIntoCol(elem, cols[i]);
if (inserted) {
break;
}
}
if (!inserted) {
var idx = this._addCol(index, item.group);
this._insertIntoCol(elem, cols[idx]);
}
this._setGroupHeader(index);
},
_setGroupHeader: function _setGroupHeader(index) {
var _this = this;
var c = this.config;
var getHeaderText = function getHeaderText(col) {
var ans = null;
var item = _this.getEvent(col.children[0]);
if (item) {
ans = item.group;
if (c.groupHeaderText) {
if (typeof c.groupHeaderText === 'string') {
if (item && hasProp(item, c.groupHeaderText)) {
ans = item[c.groupHeaderText] || '';
}
} else if (typeof c.groupHeaderText === 'function') {
ans = c.groupHeaderText(item.group);
}
}
}
return ans;
};
var tr = this.el.groupHeaders[index].querySelector('tr');
tr.innerHTML = '';
if (c.labelGroups) {
var cols = this.el.eventsContainers[index].children;
var colspan = 1;
var group = cols[0] && cols[0].dataset['group'];
var headerText = getHeaderText(cols[0]);
var thObj = {
tagName: 'th',
style: {},
children: [{
tagName: 'div',
attr: {
title: headerText
},
children: headerText
}]
};
if (cols.length > 1) {
for (var i = 1; i < cols.length; i++) {
var col = cols[i];
if (col.dataset['group'] !== group) {
// new group
thObj.style.width = colspan / cols.length * 100 + '%';
var _headerText2 = getHeaderText(cols[i - 1]);
thObj.children[0].children = _headerText2;
thObj.children[0].attr.title = _headerText2;
tr.appendChild(createElem(thObj));
group = col.dataset['group'];
colspan = 1;
} else {
colspan += 1;
}
}
thObj.style.width = colspan / cols.length * 100 + '%';
var _headerText = getHeaderText(cols[cols.length - 1]);
thObj.children[0].children = _headerText;
thObj.children[0].attr.title = _headerText;
tr.appendChild(createElem(thObj)); // last group
} else if (thObj.children[0].children !== undefined && thObj.children[0].children !== null) {
tr.appendChild(createElem(thObj)); // only one group
}
clearTimeout(timeoutRef);
timeoutRef = setTimeout(function () {
clearTimeout(timeoutRef);
timeoutRef = null;
_this._coords.grid = getRect(_this.el.dayGrids[0], _this.el.scroll);
}, 0);
}
},
_arrangeEvents: function _arrangeEvents(dateIndex) {
var con = this.el.eventsContainers[dateIndex];
var cols = con.children; // remove dataset['group'] / remove empty cols
for (var i = 0; i < cols.length; i++) {
if (cols[i].childElementCount === 0) {
if (i === 0) {
cols[i].removeAttribute('data-group');
} else {
con.removeChild(cols[i]);
i -= 1;
}
}
}
if (cols.length > 1) {
// begin from col[1], for each event block inside, try to push the block left
for (var _i2 = 1; _i2 < cols.length; _i2++) {
var blocks = cols[_i2].children;
for (var j = 0; j < blocks.length; j++) {
var inserted = false;
var k = 0;
while (!inserted && k < _i2) {
inserted = this._insertIntoCol(blocks[j], cols[k]);
k += 1;
}
if (inserted) {
j -= 1;
}
}
} // remove empty cols
if (cols.length > 1) {
for (var _i3 = 0; _i3 < cols.length; _i3++) {
if (cols[_i3].childElementCount === 0) {
con.removeChild(cols[_i3]);
_i3 -= 1;
}
}
}
}
this._setGrid(dateIndex, cols.length);
this._setGroupHeader(dateIndex);
},
_addCol: function _addCol(index, group) {
var col = createElem({
tagName: 'DIV',
className: 'events-col'
});
var idx = -1;
var con = this.el.eventsContainers[index];
if (group !== undefined) {
group = String(group);
col.dataset['group'] = group;
var fellows = con.querySelectorAll('[data-group="' + group + '"]');
if (fellows.length) {
fellows[fellows.length - 1].insertAdjacentElement('afterEnd', col);
idx = [].indexOf.call(con.children, col);
} else {
con.appendChild(col);
idx = con.children.length - 1;
}
} else {
con.appendChild(col);
idx = con.children.length - 1;
}
this._setGrid(index, con.children.length);
return idx;
},
_insertIntoCol: function _insertIntoCol(elem, col) {
var nr = elem.dataset['nr'];
var index = col.parentElement.dataset['index'];
var _item = this._events[index][nr];
var group = _item.group;
if (group !== undefined) {
group = String(group); // null will also be a valid group
}
var s = _item.startm;
var e = _item.endm;
var items = col.children;
if (items.length) {
if (col.dataset['group'] !== group) {
return false;
}
var hasSpace = true;
for (var i = 0; i < items.length; i++) {
if (items[i] !== elem) {
var se = this._posToMinute(items[i]);
if (!(se.startm >= e || se.endm <= s)) {
// overlap
hasSpace = false;
break;
}
}
}
if (hasSpace) {
col.appendChild(elem);
return true;
} else {
return false;
}
} else {
if (group !== undefined) {
col.dataset['group'] = String(group);
}
col.appendChild(elem);
return true;
}
},
_clearDom: function _clearDom(which) {
var _this2 = this;
var pool = this._toDateIndexList(which);
pool.forEach(function (index) {
var blocks = _this2.el.eventsContainers[index].getElementsByClassName('event');
for (var i = 0; i < blocks.length; i++) {
blocks[i].parentElement.removeChild(blocks[i]);
i -= 1;
}
_this2._renderInner(index);
_this2._setGroupHeader(index);
});
}
};
var publics = {
addEvent: function addEvent(eventItem) {
var item = this._tsItem(eventItem);
if (item) {
var i = 0;
var _pool = this._events[item.dateIndex];
var pool = this.events[item.dateIndex];
while (i < _pool.length && _pool[i].startm <= item.startm) {
i += 1;
}
item.nr = i; // nr is for locating data provided for modify event
i = _pool.length - 1;
while (i >= item.nr) {
// update nr of those behind. important: decrease i
this.el.dayCols[item.dateIndex].querySelector('[data-nr="' + _pool[i].nr + '"]').dataset['nr'] = _pool[i].nr + 1;
_pool[i].nr += 1;
i -= 1;
}
_pool.splice(item.nr, 0, item);
pool.splice(item.nr, 0, eventItem);
this._renderEvent(item);
}
return this;
},
updateEvent: function updateEvent(coords, modified) {
var c = this.config;
var valid = Array.isArray(coords) && coords.length >= 2 && coords[0] >= 0 && coords[0] < c.num && coords[1] >= 0 && coords[1] < this.events[coords[0]].length;
var _modified = this._tsItem(modified);
if (valid && _modified) {
var dateIndex = coords[0];
var nr = coords[1];
if (_modified.dateIndex !== dateIndex) {
this.deleteEvent(coords);
this.addEvent(modified);
this._arrangeEvents(_modified.dateIndex);
} else {
_modified.nr = nr;
this.events[dateIndex][nr] = modified;
this._events[dateIndex][nr] = _modified;
var block = this.el.eventsContainers[dateIndex].querySelector('[data-nr="' + nr + '"]');
var top = this._minuteToTop(_modified.startm);
if (block && top >= 0) {
Object.assign(block.style, {
top: top + 'px',
height: this._minuteToTop(_modified.endm) - top + 'px'
});
this._insertIntoDate(block, dateIndex);
this._arrangeEvents(dateIndex);
}
}
}
return this;
},
deleteEvent: function deleteEvent(coords) {
var c = this.config;
var valid = Array.isArray(coords) && coords.length >= 2 && coords[0] >= 0 && coords[0] < c.num && coords[1] >= 0 && coords[1] < this.events[coords[0]].length;
if (valid) {
var dateIndex = coords[0];
var nr = coords[1];
var _pool = this._events[dateIndex];
var pool = this.events[dateIndex];
_pool.splice(nr, 1);
pool.splice(nr, 1);
var el = this.el.dayCols[dateIndex].querySelector('[data-nr="' + nr + '"]');
el.parentElement.removeChild(el);
var i = nr;
while (i < _pool.length) {
// update nr of those behind
this.el.dayCols[dateIndex].querySelector('[data-nr="' + _pool[i].nr + '"]').dataset['nr'] = _pool[i].nr - 1;
_pool[i].nr -= 1;
i += 1;
}
this._arrangeEvents(dateIndex);
}
return this;
},
/**
*
* @param {*} which
*/
clear: function clear(which) {
var _this = this;
this._clearDom(which);
var pool = this._toDateIndexList(which);
pool.forEach(function (index) {
_this.events[index] = [];
_this._events[index] = [];
});
return this;
},
rerender: function rerender() {
var _this2 = this;
var c = this.config;
this._clearDom();
for (var i = 0; i < c.num; i++) {
this._events[i].forEach(function (item) {
_this2._renderEvent(item);
});
this._arrangeEvents(i);
}
return this;
},
changeStartDate: function changeStartDate(date) {
var _this3 = this;
var c = this.config;
if (date instanceof Date && date.toString() !== 'Invalid Date') {
(function () {
var d = new Date(date);
var _d = new Date(c.startDate);
d.setHours(0, 0, 0, 0);
_d.setHours(0, 0, 0, 0);
var offset = (d.getTime() - _d.getTime()) / 1000 / 60 / 60 / 24;
var theadEl = _this3.el.root.querySelector('thead');
if (offset !== 0 && theadEl) {
c.startDate = d;
_this3._genDates(true); // events, _events are preserved
var thead = createElem(_this3._genThead());
theadEl.innerHTML = thead.innerHTML;
if (offset < c.num && offset > 0) {
for (var i = offset; i < c.num; i++) {
_this3._events[i].forEach(function (item) {
item.dateIndex -= offset;
});
}
_this3.events.splice(0, offset);
_this3._events.splice(0, offset);
for (var _i = 0; _i < offset; _i++) {
_this3.events.push([]);
_this3._events.push([]);
}
_this3.rerender();
} else if (offset < 0 && offset > -c.num) {
for (var _i2 = 0; _i2 < c.num + offset; _i2++) {
_this3._events[_i2].forEach(function (item) {
item.dateIndex -= offset;
});
}
_this3.events.splice(c.num + offset, -offset);
_this3._events.splice(c.num + offset, -offset);
for (var _i3 = 0; _i3 < -offset; _i3++) {
_this3.events.unshift([]);
_this3._events.unshift([]);
}
_this3.rerender();
} else {
_this3.clear();
}
}
})();
}
return this;
},
destroy: function destroy() {
this._unbind();
var ch = this.el.root.children;
for (var i = 0; i < ch.length; i++) {
this.el.root.removeChild(ch[i]);
i -= 1;
}
for (var key in this.el) {
this.el[key] = null;
}
this.dates = [];
this.events = [];
this._events = [];
},
getEvent: function getEvent(elem) {
if (elem && elem instanceof HTMLElement) {
var coords = this._getEventCoords(elem);
return this.events[coords[0]][coords[1]];
}
return null;
},
getElem: function getElem(eventItem) {
if (eventItem) {
for (var i = 0; i < this.events.length; i++) {
var j = this.events[i].indexOf(eventItem);
if (j > -1) {
return this.el.dayCols[i].querySelector('[data-nr="' + j + '"]');
}
}
}
return null;
},
updateRect: function updateRect() {
this._coords.grid = getRect(this.el.dayGrids[0]);
this._coords.scroll = getRect(this.el.scroll);
return this;
}
};
var timeRegex = /^(?:[01][0-9]|2[0-3]):[0-5][0-9]$/;
var helpers = {
_posToMinute: function _posToMinute(elem, recalc) {
if (!recalc && elem.dataset['startm'] && elem.dataset['endm']) {
return {
startm: parseInt(elem.dataset['startm']),
endm: parseInt(elem.dataset['endm'])
};
}
var c = this.config;
var top = elem.offsetTop;
var h = elem.offsetHeight;
var startm = c.dayStart * 60 + top / CELL_HEIGHT * c.gap;
var endm = startm + h / CELL_HEIGHT * c.gap;
return {
startm: startm,
endm: endm
};
},
_minuteToTop: function _minuteToTop(m) {
m = parseInt(m);
var ans = -1;
if (typeof m === 'number' && !isNaN(m)) {
var c = this.config;
ans = (m - c.dayStart * 60) / c.gap * CELL_HEIGHT;
}
return ans;
},
_quantizeH: function _quantizeH(h) {
return Math.round(h / CELL_HEIGHT) * CELL_HEIGHT;
},
_quantize: function _quantize(y) {
var top = this._coords.grid.top;
var scrolled = this.el.scroll.scrollTop;
var raw = y - top + scrolled;
return this._quantizeH(raw) + top - scrolled;
},
_quantizeTop: function _quantizeTop(t) {
var top = this._coords.grid.top - this.el.scroll.scrollTop;
return this._quantize(t + top) - top;
},
_getEventCoords: function _getEventCoords(elem) {
var nr = parseInt(elem.dataset['nr']);
var index = parseInt(closest(elem, '.events-container').dataset['index']);
return [index, nr];
},
/**
* validate eventItem, if valid, return a clone with additional info
* Note: nr is not added here
* @param {Object} eventItem
*/
_tsItem: function _tsItem(eventItem) {
if (!eventItem) {
return null;
}
var valid = true;
valid &= eventItem.date && parseDate(eventItem.date).toString() !== 'Invalid Date';
valid &= eventItem.start && timeRegex.test(eventItem.start) && eventItem.end && timeRegex.test(eventItem.end);
if (valid) {
var c = this.config;
var dayStartm = c.dayStart * 60;
var dayEndm = c.dayEnd * 60;
var parsedDate = parseDate(eventItem.date);
var ymd = fullDate(parsedDate);
var startm = timeStrToMinute(eventItem.start);
var endm = timeStrToMinute(eventItem.end);
var dateIndex = -1;
for (var i = 0; i < this.dates.length; i++) {
if (ymd === fullDate(this.dates[i])) {
dateIndex = i;
break;
}
}
if (dateIndex > -1 && startm < endm && startm >= dayStartm && endm <= dayEndm) {
return Object.assign({}, eventItem, {
parsedDate: parsedDate,
ymd: ymd,
startm: startm,
endm: endm,
dateIndex: dateIndex
});
} else {
return null;
}
} else {
return null;
}
},
_toDateIndex: function _toDateIndex(what) {
var c = this.config;
if (typeof what === 'number') {
if (what < c.num && what > -1) {
return what;
} else {
return -1;
}
}
if (!what) {
return -1;
}
if (typeof what === 'string') {
var parsed = parseDate(what);
if (parsed.toString() !== 'Invalid Date') {
var index = -1;
for (var i = 0; i < this.dates.length; i++) {
if (fullDate(parsed) === fullDate(this.dates[i])) {
index = i;
break;
}
}
return index;
} else {
return -1;
}
}
if (_typeof(what) === 'object' && what instanceof Date && what.toString() !== 'Invalid Date') {
var _index = -1;
for (var _i = 0; _i < this.dates.length; _i++) {
if (fullDate(what) === fullDate(this.dates[_i])) {
_index = _i;
break;
}
}
return _index;
}
return -1;
},
_toDateIndexList: function _toDateIndexList(which) {
var _this = this;
var c = this.config;
var pool = [];
if (which === undefined) {
for (var i = 0; i < c.num; i++) {
pool.push(i);
}
} else if (Array.isArray(which)) {
var p = which.map(function (w) {
return _this._toDateIndex(w);
});
p.forEach(function (q) {
if (q > -1 && pool.indexOf(q) === -1) {
pool.push(q);
}
});
} else {
var index = this._toDateIndex(which);
if (index > -1) {
pool.push(index);
}
}
return pool;
}
};
var touchStartEl = null;
var touchData = {
startY: 0,
startX: 0,
yArr: [],
xArr: [],
timestamp: Date.now()
};
var touchTO = null;
var drawing = null;
var modifying = null;
var modifyingH = 0;
var modifyingTop = 0;
var yTriggered = false;
var dragging = null;
var oPos;
var colLeft = [];
var dropIndex;
var bound = {
top: -Infinity,
right: Infinity,
bottom: Infinity,
left: -Infinity
};
var autoScroll = function () {
var scrollInt = {
h: null,
v: null
};
var velocity = {
h: 0,
v: 0
};
var initScroll = {
h: 0,
v: 0
};
var hasRaf = window.requestAnimationFrame && typeof requestAnimationFrame === 'function';
var set = function set(d, v) {
var el = this.el.scroll;
velocity[d] = v;
var tick = function tick() {
if (d === 'h') {
el.scrollLeft += velocity[d];
} else {
el.scrollTop += velocity[d];
}
if (hasRaf) {
scrollInt[d] = requestAnimationFrame(tick);
}
};
if (v === 0) {
reset.call(this, d);
} else {
if (!scrollInt[d]) {
if (hasRaf) {
scrollInt[d] = requestAnimationFrame(tick);
} else {
scrollInt[d] = setInterval(tick, 16);
}
}
}
};
var reset = function reset(d) {
var t = [];
if (!d) {
t = ['h', 'v'];
} else {
t.push(d);
}
var delta = {
h: this.el.scroll.scrollLeft - initScroll.h,
v: this.el.scroll.scrollTop - initScroll.v
};
t.forEach(function (di) {
if (hasRaf) {
cancelAnimationFrame(scrollInt[di]);
} else {
clearInterval(scrollInt[di]);
}
scrollInt[di] = null;
velocity[di] = 0;
});
record.call(this, d);
return delta;
};
var scrolling = function scrolling(d) {
return velocity[d] !== 0;
};
var record = function record(d) {
if (!d || d === 'h') {
initScroll.h = this.el.scroll.scrollLeft;
}
if (!d || d === 'v') {
initScroll.v = this.el.scroll.scrollTop;
}
};
return {
set: set,
reset: reset,
scrolling: scrolling,
record: record,
initScroll: initScroll
};
}();
var land = function land() {
touchData.startY = 0;
touchData.yArr = [];
touchData.startX = 0;
touchData.xArr = [];
touchData.timestamp = Date.now();
touchStartEl = null;
drawing = null;
modifying = null;
dragging = null;
yTriggered = false;
modifyingH = 0;
modifyingTop = 0;
bound = {
top: -Infinity,
right: Infinity,
bottom: Infinity,
left: -Infinity
};
};
var getXY = function getXY(e) {
if (/mouse/.test(e.type)) {
return {
x: e.clientX,
y: e.clientY
};
} else {
return {
x: e.touches[0].clientX,
y: e.touches[0].clientY
};
}
};
var preventClick = function preventClick(e) {
e.stopImmediatePropagation();
};
var preventContextMenu = function preventContextMenu(e) {
e.preventDefault();
e.stopPropagation();
return false;
};
var scrollDetector = function scrollDetector() {
// TODO: if no scroll container provided, <html> is chosen, which doesn't seem to response to scroll event
// may be auto add a scroll container?
clearTimeout(touchTO);
touchTO = null;
};
var handlers = {
touchstart: function touchstart(e) {
var c = this.config;
var xy = getXY(e);
var eventY = xy.y;
if (c.quantizing ^ e.shiftKey) {
eventY = this._quantize(eventY);
}
var common = function () {
touchData.timestamp = Date.now();
touchData.startY = xy.y;
touchData.yArr.push(eventY);
var eventX = xy.x;
touchData.startX = eventX;
touchData.xArr.push(eventX);
document.addEventListener('mousemove', this._handlers.touchmove, {
passive: false
});
document.addEventListener('touchmove', this._handlers.touchmove, {
passive: false
});
document.addEventListener('mouseup', this._handlers.touchend);
document.addEventListener('touchend', this._handlers.touchend);
}.bind(this);
var catchers = [['.event-bar', function (e, el) {
common();
touchStartEl = el;
el.style.opacity = 1;
modifying = closest(el, '.event');
var offset = getOffset(modifying);
modifyingH = offset.height;
modifyingTop = offset.top;
bound.top = this._coords.grid.top;
bound.bottom = this._coords.scroll.top + getOffset(this.el.scroll).height;
modifying.removeEventListener('click', preventClick);
}], ['.event', function (e, el) {
common();
touchStartEl = el;
var root = this.el.root;
var scroll = this.el.scroll;
modifying = el;
var offset = getOffset(modifying);
modifyingTop = offset.top;
modifyingH = offset.height;
oPos = getRect(el, scroll);
colLeft = [].map.call(this.el.dayCols, function (col) {
return getRect(col, scroll).left;
});
dropIndex = -1;
autoScroll.record.call(this);
var sPos = this._coords.scroll;
var oFixPos = getFixRect(el);
var sOffset = getOffset(scroll);
var tOffset = getOffset(touchStartEl);
bound = {
top: xy.y - oFixPos.top + sPos.top + getOffset(root.querySelector('thead')).height,
right: sPos.left + sOffset.width - (tOffset.width - (xy.x - oFixPos.left)),
bottom: sPos.top + sOffset.height - (tOffset.height - (xy.y - oFixPos.top)),
left: xy.x - oFixPos.left + sPos.left + getOffset(root.querySelector('.time-line')).width
}; // won't work when in touchend handler
touchStartEl.removeEventListener('click', preventClick);
}], ['.events-col', function (e, el) {
var _this = this;
if (el === e.target) {
// start drawing only when touch on .events-col
common();
var starter = function starter() {
touchStartEl = el;
var top = eventY - _this._coords.grid.top + _this.el.scroll.scrollTop;
drawing = createElem({
tagName: 'DIV',
className: 'event drawing',
style: {
top: top + 'px'
}
});
el.appendChild(drawing);
}; // if touch - set a timer, unset when touchend
if (e.type === 'touchstart') {
touchTO = setTimeout(function () {
starter();
}, TOUCH_DELAY);
window.addEventListener('contextmenu', preventContextMenu);
this.el.scroll.addEventListener('scroll', scrollDetector);
} else {
starter();
}
} else if (e.target.className !== 'event-bar' && el.contains(e.target)) {
e.stopPropagation();
}
}]];
for (var i = 0; i < catchers.length; i++) {
var selector = catchers[i][0];
var catcher = catchers[i][1].bind(this);
var el = getDelegate(e, selector);
if (el) {
this.el.root.style.userSelect = 'none';
catcher(e, el);
break;
}
}
},
touchmove: function touchmove(e) {
var c = this.config;
var xy = getXY(e);
var eventY = xy.y;
var startY = touchData.startY;
var doQ = c.quantizing ^ e.shiftKey;
if (doQ) {
eventY = this._quantize(eventY);
startY = this._quantize(startY);
}
touchData.yArr.push(eventY);
var eventX = xy.x;
var startX = touchData.startX;
touchData.xArr.push(eventX);
var movedY = eventY - startY;
var movedX = eventX - startX;
if (touchStartEl) {
e.preventDefault();
if (touchStartEl.className === 'events-col' && drawing && movedY > 0) {
drawing.style.height = movedY + 'px';
} else if (touchStartEl.className === 'event-bar' && modifying) {
if (Math.abs(movedY) >= c.stretchThreshold / c.gap * CELL_HEIGHT || yTriggered) {
if (!yTriggered) {
modifying.addEventListener('click', preventClick);
}
yTriggered = true;
var h;
if (xy.y >= bound.top && xy.y <= bound.bottom) {
var sV = autoScroll.scrolling('v');
if (sV) {
autoScroll.reset.call(this, 'v');
}
h = eventY - this._coords.grid.top - modifyingTop + this.el.scroll.scrollTop;
if (!doQ) {
// otherwise bottom will move suddenly to where the handle bar was
h = modifyingH + movedY;
}
} else {
if (xy.y < bound.top) {
autoScroll.set.call(this, 'v', xy.y - bound.top);
}
if (xy.y > bound.bottom) {
autoScroll.set.call(this, 'v', xy.y - bound.bottom);
}
}
if (h > -0.1 && modifyingTop + h <= (c.dayEnd - c.dayStart) * 60 / c.gap * CELL_HEIGHT) {
modifying.style.height = Math.max(h, 0) + 'px';
if (Math.round(h) >= c.createThreshold / c.gap * CELL_HEIGHT) {
modifying.classList.remove('drawing');
} else {
modifying.classList.add('drawing');
}
}
}
} else if (touchStartEl.classList.contains('event') && modifying) {
var root = this.el.root;
var scroll = this.el.scroll;
if (Math.abs(movedX) > MOVE_X_THRESHOLD) {
var fixTop = oPos.top - scroll.scrollTop;
var fixLeft = oPos.left - scroll.scrollLeft;
if (!dragging) {
dragging = touchStartEl.cloneNode(true);
Object.assign(dragging.style, {
cursor: 'move',
position: 'fixed',
top: fixTop + 'px',
left: fixLeft + 'px',
width: touchStartEl.offsetWidth + 'px',
opacity: 0.5
});
root.appendChild(dragging);
}
if (xy.x > bound.left && xy.x < bound.right) {
var left = fixLeft + movedX;
var sH = autoScroll.scrolling('h');
if (sH) {
var dh = autoScroll.reset.call(this, 'h').h;
touchData.startX -= dh; // update old data
left += dh;
}
dragging.style.left = left + 'px';
} else {
if (xy.x < bound.left) {
autoScroll.set.call(this, 'h', xy.x - bound.left);
}
if (xy.x > bound.right) {
autoScroll.set.call(this, 'h', xy.x - bound.right);
}
}
if (xy.y > bound.top && xy.y < bound.bottom) {
var top = (doQ ? this._quantize(fixTop) : fixTop) + movedY;
var _sV = autoScroll.scrolling('v');
if (_sV) {
var dv = autoScroll.reset.call(this, 'v').v;
touchData.startY -= dv;
top += dv;
}
dragging.style.top = top + 'px';
} else {
if (xy.y < bound.top) {
autoScroll.set.call(this, 'v', xy.y - bound.top);
}
if (xy.y > bound.bottom) {
autoScroll.set.call(this, 'v', xy.y - bound.bottom);
}
}
dropIndex = -1;
var srcIndex = parseInt(closest(touchStartEl, '.events-container').dataset['index']);
for (var i = colLeft.length - 1; i >= 0; i--) {
if (colLeft[i] < eventX + scroll.scrollLeft) {
if (i !== srcIndex) {
dropIndex = i;
}
break;
}
}
[].forEach.call(this.el.dayCols, function (col, i) {
if (i === dropIndex) {
col.classList.add('drop-target');
} else {
col.classList.remove('drop-target');
}
});
if (dropIndex > -1) {
this.el.dayCols[dropIndex].classList.add('drop-target');
}
} else {
[].forEach.call(this.el.dayCols, function (col) {
col.classList.remove('drop-target');
});
if (dragging) {
this.el.root.removeChild(dragging);
dragging = null;
}
if (Math.abs(movedY) >= c.moveYThreshold / c.gap * CELL_HEIGHT || yTriggered) {
// when triggered, ignore the threshold
if (!yTriggered) {
// specific fix so that delegated click event handler won't be called
touchStartEl.addEventListener('click', preventClick);
}
yTriggered = true;
touchStartEl.style.cursor = 'move';
if (xy.y > bound.top && xy.y < bound.bottom) {
Object.assign(touchStartEl.style, {
position: '',
left: '',
width: ''
});
if (doQ) {
var _top = this._quantizeTop(modifyingTop) + movedY;
var _sV2 = autoScroll.scrolling('v');
if (_sV2) {
var _dv = autoScroll.reset.call(this, 'v').v;
touchData.startY -= _dv;
_top += _dv;
}
if (_top >= 0 && _top + modifyingH <= (c.dayEnd - c.dayStart) * 60 / c.gap * CELL_HEIGHT) {
touchStartEl.style.top = _top + 'px';
}
} else {
touchStartEl.style.top = modifyingTop + movedY + 'px';
}
} else {
var _fixTop = oPos.top - autoScroll.initScroll.v;
var _fixLeft = oPos.left - autoScroll.initScroll.h;
Object.assign(touchStartEl.style, {
position: 'fixed',
left: _fixLeft + 'px',
width: touchStartEl.offsetWidth + 'px'
});
if (xy.y < bound.top) {
autoScroll.set.call(this, 'v', xy.y - bound.top);
touchStartEl.style.top = bound.top - (touchData.startY - _fixTop) + 'px';
}
if (xy.y > bound.bottom) {
autoScroll.set.call(this, 'v', xy.y - bound.bottom);
touchStartEl.style.top = bound.bottom - (touchData.startY - _fixTop) + 'px';
}
}
}
}
}
}
},
touchend: function touchend(e) {
this.el.root.style.userSelect = '';
clearTimeout(touchTO);
touchTO = null;
window.removeEventListener('contextmenu', preventContextMenu);
this.el.scroll.removeEventListener('scroll', scrollDetector);
if (!touchStartEl) {
return;
}
var c = this.config;
var doQ = c.quantizing ^ e.shiftKey;
var lastY = touchData.yArr[touchData.yArr.length - 1];
if (touchStartEl.className === 'events-col' && drawing) {
var se = this._posToMinute(drawing);
touchStartEl.removeChild(drawing);
if (se.endm - se.startm >= c.createThreshold) {
var index = parseInt(touchStartEl.parentElement.dataset['index']);
var date = fullDate(this.dates[index]);
var item = {
date: date,
start: minuteToTimeStr(se.startm),
end: minuteToTimeStr(se.endm)
};
dispatchEvent(this.el.root, EVENT.create, {
item: item
});
if (c.directChange === true || includesAny(c.directChange, EVENT.create)) {
this.addEvent(item);
}
}
land();
} else if (touchStartEl.className === 'event-bar' && modifying) {
touchStartEl.style.opacity = '';
modifying.classList.remove('drawing');
if (Math.abs(modifying.offsetHeight - modifyingH) > 0) {
var _se = this._posToMinute(modifying, true);
var coords = this._getEventCoords(modifying);
var _item2 = this.events[coords[0]][coords[1]];
var mod = {
type: 'end',
date: _item2.date,
start: _item2.start,
end: minuteToTimeStr(_se.endm)
};
if (modifying.offsetHeight >= c.createThreshold / c.gap * CELL_HEIGHT) {
modifying.style.height = modifyingH + 'px';
dispatchEvent(this.el.root, EVENT.modify, {
item: _item2,
coords: coords,
mod: mod
});
if (c.directChange === true || includesAny(c.directChange, [EVENT.modify, 'end'])) {
_item2.end = mod.end;
this.updateEvent(coords, _item2);
modifying.dataset['startm'] = _se.startm;
modifying.dataset['endm'] = _se.endm;
}
} else {
modifying.style.height = modifyingH + 'px';
dispatchEvent(this.el.root, EVENT.remove, {
item: _item2,
coords: coords
});
if (c.directChange === true || includesAny(c.directChange, EVENT.remove)) {
this.deleteEvent(coords);
}
}
} else {
modifying.style.height = modifyingH + 'px';
}
land();
autoScroll.reset.call(this);
} else if (touchStartEl.classList.contains('event')) {
if (dragging && modifying) {
if (dropIndex > -1) {
var top = Math.round(parseFloat(dragging.style.top) + this.el.scroll.scrollTop - this._coords.grid.top);
top = Math.max(top, 0);
top = Math.min(top, (c.dayEnd - c.dayStart) * 60 / c.gap * CELL_HEIGHT - modifyingH);
if (doQ) {
top = this._quantizeTop(top);
}
var startm = c.dayStart * 60 + top / CELL_HEIGHT * c.gap;
var endm = startm + Math.round(modifyingH / CELL_HEIGHT * c.gap);
var _coords = this._getEventCoords(modifying);
var _item3 = this.events[_coords[0]][_coords[1]];
var _