Browse Source

Add a vertical scroll option for timeline [solves #273, #1060, #466] (#2196)

* Add initial scroller without options
* Add initial scroll without an option
* Add verticalScroll option
* Fix scrollbar positions
* Add docs
* fix example
* remove jquery dependency
* Fix example
* Fix review comments
codeClimate
yotamberk 8 years ago
committed by Alexander Wunschik
parent
commit
b889a5057d
8 changed files with 249 additions and 46 deletions
  1. +8
    -0
      docs/timeline/index.html
  2. +92
    -0
      examples/timeline/other/verticalScroll.html
  3. +86
    -40
      lib/timeline/Core.js
  4. +1
    -1
      lib/timeline/Timeline.js
  5. +13
    -4
      lib/timeline/component/ItemSet.js
  6. +22
    -1
      lib/timeline/component/css/panel.css
  7. +1
    -0
      lib/timeline/optionsTimeline.js
  8. +26
    -0
      lib/util.js

+ 8
- 0
docs/timeline/index.html View File

@ -1024,6 +1024,14 @@ function (option, path) {
</td> </td>
</tr> </tr>
<tr>
<td>verticalScroll</td>
<td>Boolean</td>
<td>false</td>
<td> Show a vertical scroll on the side of the group list.
</td>
</tr>
<tr> <tr>
<td>width</td> <td>width</td>
<td>String or Number</td> <td>String or Number</td>

+ 92
- 0
examples/timeline/other/verticalScroll.html View File

@ -0,0 +1,92 @@
<html>
<head>
<title>Timeline | Vertical Scroll Option</title>
<script src="../../../dist/vis.js"></script>
<link href="../../../dist/vis.css" rel="stylesheet" type="text/css" />
<script src="../../googleAnalytics.js"></script>
</head>
<body>
<h1>Timeline vertical scroll option</h1>
<h2>With <code>
verticalScroll: true,
zoomKey: 'ctrlKey'</code>
</h2>
<div id="mytimeline1"></div>
<h2>With <code>
horizontalScroll: true,
verticalScroll: true,
zoomKey: 'ctrlKey'</code>
</h2>
<div id="mytimeline2"></div>
<script>
// create groups
var numberOfGroups = 25;
var groups = new vis.DataSet()
for (var i = 0; i < numberOfGroups; i++) {
groups.add({
id: i,
content: 'Truck&nbsp;' + i
})
}
// create items
var numberOfItems = 1000;
var items = new vis.DataSet();
var itemsPerGroup = Math.round(numberOfItems/numberOfGroups);
for (var truck = 0; truck < numberOfGroups; truck++) {
var date = new Date();
for (var order = 0; order < itemsPerGroup; order++) {
date.setHours(date.getHours() + 4 * (Math.random() < 0.2));
var start = new Date(date);
date.setHours(date.getHours() + 2 + Math.floor(Math.random()*4));
var end = new Date(date);
items.add({
id: order + itemsPerGroup * truck,
group: truck,
start: start,
end: end,
content: 'Order ' + order
});
}
}
// specify options
var options = {
stack: true,
verticalScroll: true,
zoomKey: 'ctrlKey',
maxHeight: 200,
start: new Date(),
end: new Date(1000*60*60*24 + (new Date()).valueOf()),
editable: true,
margin: {
item: 10, // minimal margin between items
axis: 5 // minimal margin between items and the axis
},
orientation: 'top'
};
// create a Timeline
options1 = Object.assign({}, options)
var container1 = document.getElementById('mytimeline1');
timeline1 = new vis.Timeline(container1, items, groups, options1);
options2 = Object.assign({horizontalScroll: true}, options)
var container2 = document.getElementById('mytimeline2');
timeline2 = new vis.Timeline(container2, items, groups, options2);
</script>
</body>
</html>

+ 86
- 40
lib/timeline/Core.js View File

@ -82,7 +82,6 @@ Core.prototype._create = function (container) {
this.dom.centerContainer.appendChild(this.dom.center); this.dom.centerContainer.appendChild(this.dom.center);
this.dom.leftContainer.appendChild(this.dom.left); this.dom.leftContainer.appendChild(this.dom.left);
this.dom.rightContainer.appendChild(this.dom.right); this.dom.rightContainer.appendChild(this.dom.right);
this.dom.centerContainer.appendChild(this.dom.shadowTop); this.dom.centerContainer.appendChild(this.dom.shadowTop);
this.dom.centerContainer.appendChild(this.dom.shadowBottom); this.dom.centerContainer.appendChild(this.dom.shadowBottom);
this.dom.leftContainer.appendChild(this.dom.shadowTopLeft); this.dom.leftContainer.appendChild(this.dom.shadowTopLeft);
@ -90,9 +89,26 @@ Core.prototype._create = function (container) {
this.dom.rightContainer.appendChild(this.dom.shadowTopRight); this.dom.rightContainer.appendChild(this.dom.shadowTopRight);
this.dom.rightContainer.appendChild(this.dom.shadowBottomRight); this.dom.rightContainer.appendChild(this.dom.shadowBottomRight);
// size properties of each of the panels
this.props = {
root: {},
background: {},
centerContainer: {},
leftContainer: {},
rightContainer: {},
center: {},
left: {},
right: {},
top: {},
bottom: {},
border: {},
scrollTop: 0,
scrollTopMin: 0
};
this.on('rangechange', function () { this.on('rangechange', function () {
if (this.initialDrawDone === true) { if (this.initialDrawDone === true) {
this._redraw(); // this allows overriding the _redraw method
this._redraw();
} }
}.bind(this)); }.bind(this));
this.on('touch', this._onTouch.bind(this)); this.on('touch', this._onTouch.bind(this));
@ -154,15 +170,15 @@ Core.prototype._create = function (container) {
}.bind(this)); }.bind(this));
function onMouseWheel(event) { function onMouseWheel(event) {
if (me.isActive()) {
me.emit('mousewheel', event);
if (this.isActive()) {
this.emit('mousewheel', event);
} }
// prevent scrolling when zoomKey defined or activated // prevent scrolling when zoomKey defined or activated
if (!me.options.zoomKey || event[me.options.zoomKey]) return
if (!this.options.zoomKey || event[this.options.zoomKey]) return
// prevent scrolling vertically when horizontalScroll is true // prevent scrolling vertically when horizontalScroll is true
if (me.options.horizontalScroll) return
if (this.options.horizontalScroll) return
var delta = 0; var delta = 0;
if (event.wheelDelta) { /* IE/Opera. */ if (event.wheelDelta) { /* IE/Opera. */
@ -173,12 +189,12 @@ Core.prototype._create = function (container) {
delta = -event.detail / 3; delta = -event.detail / 3;
} }
var current = me.props.scrollTop;
var current = this.props.scrollTop;
var adjusted = current + delta * 120; var adjusted = current + delta * 120;
if (me.isActive()) {
me._setScrollTop(adjusted);
me._redraw();
me.emit('scroll', event);
if (this.isActive()) {
this._setScrollTop(adjusted);
this._redraw();
this.emit('scroll', event);
} }
// Prevent default actions caused by mouse wheel // Prevent default actions caused by mouse wheel
@ -188,30 +204,27 @@ Core.prototype._create = function (container) {
if (this.dom.root.addEventListener) { if (this.dom.root.addEventListener) {
// IE9, Chrome, Safari, Opera // IE9, Chrome, Safari, Opera
this.dom.root.addEventListener("mousewheel", onMouseWheel, false);
this.dom.root.addEventListener("mousewheel", onMouseWheel.bind(this), false);
// Firefox // Firefox
this.dom.root.addEventListener("DOMMouseScroll", onMouseWheel, false);
this.dom.root.addEventListener("DOMMouseScroll", onMouseWheel.bind(this), false);
} else { } else {
// IE 6/7/8 // IE 6/7/8
this.dom.root.attachEvent("onmousewheel", onMouseWheel);
this.dom.root.attachEvent("onmousewheel", onMouseWheel.bind(this));
} }
// size properties of each of the panels
this.props = {
root: {},
background: {},
centerContainer: {},
leftContainer: {},
rightContainer: {},
center: {},
left: {},
right: {},
top: {},
bottom: {},
border: {},
scrollTop: 0,
scrollTopMin: 0
};
function onMouseScrollSide(event) {
var current = this.scrollTop;
var adjusted = -current;
if (me.isActive()) {
me._setScrollTop(adjusted);
me._redraw();
me.emit('scroll', event);
}
}
this.dom.left.parentNode.addEventListener('scroll', onMouseScrollSide);
this.dom.right.parentNode.addEventListener('scroll', onMouseScrollSide);
this.customTimes = []; this.customTimes = [];
@ -257,17 +270,23 @@ Core.prototype.setOptions = function (options) {
var fields = [ var fields = [
'width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'width', 'height', 'minHeight', 'maxHeight', 'autoResize',
'start', 'end', 'clickToUse', 'dataAttributes', 'hiddenDates', 'start', 'end', 'clickToUse', 'dataAttributes', 'hiddenDates',
'locale', 'locales', 'moment', 'rtl', 'zoomKey', 'horizontalScroll'
'locale', 'locales', 'moment', 'rtl', 'zoomKey', 'horizontalScroll', 'verticalScroll'
]; ];
util.selectiveExtend(fields, this.options, options); util.selectiveExtend(fields, this.options, options);
if (this.options.rtl) { if (this.options.rtl) {
var contentContainer = this.dom.leftContainer;
this.dom.leftContainer = this.dom.rightContainer;
this.dom.rightContainer = contentContainer;
this.dom.container.style.direction = "rtl"; this.dom.container.style.direction = "rtl";
this.dom.backgroundVertical.className = 'vis-panel vis-background vis-vertical-rtl'; }
this.dom.backgroundVertical.className = 'vis-panel vis-background vis-vertical-rtl';
}
if (this.options.verticalScroll) {
if (this.options.rtl) {
this.dom.rightContainer.className = 'vis-panel vis-right vis-vertical-scroll';
} else {
this.dom.leftContainer.className = 'vis-panel vis-left vis-vertical-scroll';
}
}
this.options.orientation = {item:undefined,axis:undefined}; this.options.orientation = {item:undefined,axis:undefined};
if ('orientation' in options) { if ('orientation' in options) {
@ -740,9 +759,25 @@ Core.prototype._redraw = function() {
// calculate the widths of the panels // calculate the widths of the panels
props.root.width = dom.root.offsetWidth; props.root.width = dom.root.offsetWidth;
props.background.width = props.root.width - borderRootWidth; props.background.width = props.root.width - borderRootWidth;
props.left.width = dom.leftContainer.clientWidth || -props.border.left;
if (!this.initialDrawDone) {
props.scrollbarWidth = util.getScrollBarWidth();
}
if (this.options.verticalScroll) {
if (this.options.rtl) {
props.left.width = dom.leftContainer.clientWidth || -props.border.left;
props.right.width = dom.rightContainer.clientWidth + props.scrollbarWidth || -props.border.right;
} else {
props.left.width = dom.leftContainer.clientWidth + props.scrollbarWidth || -props.border.left;
props.right.width = dom.rightContainer.clientWidth || -props.border.right;
}
} else {
props.left.width = dom.leftContainer.clientWidth || -props.border.left;
props.right.width = dom.rightContainer.clientWidth || -props.border.right;
}
props.leftContainer.width = props.left.width; props.leftContainer.width = props.left.width;
props.right.width = dom.rightContainer.clientWidth || -props.border.right;
props.rightContainer.width = props.right.width; props.rightContainer.width = props.right.width;
var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth; var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth;
props.center.width = centerWidth; props.center.width = centerWidth;
@ -796,10 +831,8 @@ Core.prototype._redraw = function() {
dom.center.style.left = '0'; dom.center.style.left = '0';
dom.center.style.top = offset + 'px'; dom.center.style.top = offset + 'px';
dom.left.style.left = '0'; dom.left.style.left = '0';
dom.left.style.top = offset + 'px';
dom.right.style.left = '0'; dom.right.style.left = '0';
dom.right.style.top = offset + 'px';
// show shadows when vertical scrolling is available // show shadows when vertical scrolling is available
var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : ''; var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : '';
var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : ''; var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : '';
@ -810,6 +843,18 @@ Core.prototype._redraw = function() {
dom.shadowTopRight.style.visibility = visibilityTop; dom.shadowTopRight.style.visibility = visibilityTop;
dom.shadowBottomRight.style.visibility = visibilityBottom; dom.shadowBottomRight.style.visibility = visibilityBottom;
if (this.options.verticalScroll) {
this.dom.shadowTopRight.style.visibility = "hidden";
this.dom.shadowBottomRight.style.visibility = "hidden";
this.dom.shadowTopLeft.style.visibility = "hidden";
this.dom.shadowBottomLeft.style.visibility = "hidden";
document.getElementsByClassName('vis-left')[0].scrollTop = -offset;
document.getElementsByClassName('vis-right')[0].scrollTop = -offset;
} else {
dom.left.style.top = offset + 'px';
dom.right.style.top = offset + 'px';
}
// enable/disable vertical panning // enable/disable vertical panning
var contentsOverflow = this.props.center.height > this.props.centerContainer.height; var contentsOverflow = this.props.center.height > this.props.centerContainer.height;
this.hammer.get('pan').set({ this.hammer.get('pan').set({
@ -832,6 +877,7 @@ Core.prototype._redraw = function() {
} else { } else {
this.redrawCount = 0; this.redrawCount = 0;
} }
this.initialDrawDone = true; this.initialDrawDone = true;
//Emit public 'changed' event for UI updates, see issue #1592 //Emit public 'changed' event for UI updates, see issue #1592

+ 1
- 1
lib/timeline/Timeline.js View File

@ -52,7 +52,6 @@ function Timeline (container, items, groups, options) {
axis: 'bottom', // axis orientation: 'bottom', 'top', or 'both' axis: 'bottom', // axis orientation: 'bottom', 'top', or 'both'
item: 'bottom' // not relevant item: 'bottom' // not relevant
}, },
rtl: false,
moment: moment, moment: moment,
width: null, width: null,
@ -61,6 +60,7 @@ function Timeline (container, items, groups, options) {
minHeight: null minHeight: null
}; };
this.options = util.deepExtend({}, this.defaultOptions); this.options = util.deepExtend({}, this.defaultOptions);
this.options.rtl = options.rtl;
// Create the DOM, props, and emitter // Create the DOM, props, and emitter
this._create(container); this._create(container);

+ 13
- 4
lib/timeline/component/ItemSet.js View File

@ -27,7 +27,6 @@ var BACKGROUND = '__background__'; // reserved group id for background items wit
function ItemSet(body, options) { function ItemSet(body, options) {
this.body = body; this.body = body;
this.defaultOptions = { this.defaultOptions = {
rtl: false,
type: null, // 'box', 'point', 'range', 'background' type: null, // 'box', 'point', 'range', 'background'
orientation: { orientation: {
item: 'bottom' // item orientation: 'top' or 'bottom' item: 'bottom' // item orientation: 'top' or 'bottom'
@ -96,7 +95,8 @@ function ItemSet(body, options) {
// options is shared by this ItemSet and all its items // options is shared by this ItemSet and all its items
this.options = util.extend({}, this.defaultOptions); this.options = util.extend({}, this.defaultOptions);
this.options.rtl = options.rtl;
// options for getting items from the DataSet with the correct type // options for getting items from the DataSet with the correct type
this.itemOptions = { this.itemOptions = {
type: {start: 'Date', end: 'Date'} type: {start: 'Date', end: 'Date'}
@ -230,7 +230,12 @@ ItemSet.prototype._create = function(){
// add item on doubletap // add item on doubletap
this.hammer.on('doubletap', this._onAddItem.bind(this)); this.hammer.on('doubletap', this._onAddItem.bind(this));
this.groupHammer = new Hammer(this.body.dom.leftContainer);
if (this.options.rtl) {
this.groupHammer = new Hammer(this.body.dom.rightContainer);
} else {
this.groupHammer = new Hammer(this.body.dom.leftContainer);
}
this.groupHammer.on('panstart', this._onGroupDragStart.bind(this)); this.groupHammer.on('panstart', this._onGroupDragStart.bind(this));
this.groupHammer.on('panmove', this._onGroupDrag.bind(this)); this.groupHammer.on('panmove', this._onGroupDrag.bind(this));
@ -451,7 +456,11 @@ ItemSet.prototype.show = function() {
// show labelset containing labels // show labelset containing labels
if (!this.dom.labelSet.parentNode) { if (!this.dom.labelSet.parentNode) {
this.body.dom.left.appendChild(this.dom.labelSet);
if (this.options.rtl) {
this.body.dom.right.appendChild(this.dom.labelSet);
} else {
this.body.dom.left.appendChild(this.dom.labelSet);
}
} }
}; };

+ 22
- 1
lib/timeline/component/css/panel.css View File

@ -1,4 +1,3 @@
.vis-panel { .vis-panel {
position: absolute; position: absolute;
@ -24,6 +23,28 @@
overflow: hidden; overflow: hidden;
} }
.vis-left.vis-panel.vis-vertical-scroll, .vis-right.vis-panel.vis-vertical-scroll {
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
}
.vis-left.vis-panel.vis-vertical-scroll {
direction: rtl;
}
.vis-left.vis-panel.vis-vertical-scroll .vis-content {
direction: ltr;
}
.vis-right.vis-panel.vis-vertical-scroll {
direction: ltr;
}
.vis-right.vis-panel.vis-vertical-scroll .vis-content {
direction: rtl;
}
.vis-panel.vis-center, .vis-panel.vis-center,
.vis-panel.vis-top, .vis-panel.vis-top,
.vis-panel.vis-bottom { .vis-panel.vis-bottom {

+ 1
- 0
lib/timeline/optionsTimeline.js View File

@ -26,6 +26,7 @@ let allOptions = {
//globals : //globals :
align: {string}, align: {string},
rtl: {boolean, 'undefined': 'undefined'}, rtl: {boolean, 'undefined': 'undefined'},
verticalScroll: {boolean, 'undefined': 'undefined'},
horizontalScroll: {boolean, 'undefined': 'undefined'}, horizontalScroll: {boolean, 'undefined': 'undefined'},
autoResize: {boolean}, autoResize: {boolean},
clickToUse: {boolean}, clickToUse: {boolean},

+ 26
- 0
lib/util.js View File

@ -1452,3 +1452,29 @@ exports.easingFunctions = {
return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
} }
}; };
exports.getScrollBarWidth = function () {
var inner = document.createElement('p');
inner.style.width = "100%";
inner.style.height = "200px";
var outer = document.createElement('div');
outer.style.position = "absolute";
outer.style.top = "0px";
outer.style.left = "0px";
outer.style.visibility = "hidden";
outer.style.width = "200px";
outer.style.height = "150px";
outer.style.overflow = "hidden";
outer.appendChild (inner);
document.body.appendChild (outer);
var w1 = inner.offsetWidth;
outer.style.overflow = 'scroll';
var w2 = inner.offsetWidth;
if (w1 == w2) w2 = outer.clientWidth;
document.body.removeChild (outer);
return (w1 - w2);
};

Loading…
Cancel
Save