Browse Source

Implemented events `click`, `doubleClick`, and `contextMenu`. Implemented method `getEventProperties(event)`.

v3_develop
jos 9 years ago
parent
commit
36cb8193d1
12 changed files with 478 additions and 281 deletions
  1. +2
    -0
      HISTORY.md
  2. +287
    -226
      dist/vis.js
  3. +1
    -1
      dist/vis.map
  4. +14
    -14
      dist/vis.min.js
  5. +6
    -6
      docs/network.html
  6. +63
    -10
      docs/timeline.html
  7. +6
    -6
      lib/timeline/Core.js
  8. +53
    -0
      lib/timeline/Timeline.js
  9. +6
    -16
      lib/timeline/component/ItemSet.js
  10. +18
    -0
      lib/util.js
  11. +10
    -2
      test/timeline.html
  12. +12
    -0
      test/timeline_groups.html

+ 2
- 0
HISTORY.md View File

@ -26,6 +26,8 @@ http://visjs.org
ctrl key down. ctrl key down.
- Implemented configuration option `order: function` to define a custom ordering - Implemented configuration option `order: function` to define a custom ordering
for the items (see #538, #234). for the items (see #538, #234).
- Implemented events `click`, `doubleClick`, and `contextMenu`.
- Implemented method `getEventProperties(event)`.
- Fixed not property initializing with a DataView for groups. - Fixed not property initializing with a DataView for groups.
- Merged add custom timebar functionality, thanks @aytech! - Merged add custom timebar functionality, thanks @aytech!
- Fixed #664: end of item not restored when canceling a move event. - Fixed #664: end of item not restored when canceling a move event.

+ 287
- 226
dist/vis.js View File

@ -827,6 +827,24 @@ return /******/ (function(modules) { // webpackBootstrap
return target; return target;
}; };
/**
* Check if given element contains given parent somewhere in the DOM tree
* @param {Element} element
* @param {Element} parent
*/
exports.hasParent = function (element, parent) {
var e = element;
while (e) {
if (e === parent) {
return true;
}
e = e.parentNode;
}
return false;
};
exports.option = {}; exports.option = {};
/** /**
@ -6576,6 +6594,16 @@ return /******/ (function(modules) { // webpackBootstrap
this.itemsData = null; // DataSet this.itemsData = null; // DataSet
this.groupsData = null; // DataSet this.groupsData = null; // DataSet
this.on('tap', function (event) {
me.emit('click', me.getEventProperties(event))
});
this.on('doubletap', function (event) {
me.emit('doubleClick', me.getEventProperties(event))
});
this.dom.root.oncontextmenu = function (event) {
me.emit('contextmenu', me.getEventProperties(event))
};
// apply options // apply options
if (options) { if (options) {
this.setOptions(options); this.setOptions(options);
@ -6800,6 +6828,49 @@ return /******/ (function(modules) { // webpackBootstrap
}; };
}; };
/**
* Generate Timeline related information from an event
* @param {Event} event
* @return {Object} An object with related information, like on which area
* The event happened, whether clicked on an item, etc.
*/
Timeline.prototype.getEventProperties = function (event) {
var item = this.itemSet.itemFromTarget(event);
var group = this.itemSet.groupFromTarget(event);
var pageX = event.gesture ? event.gesture.center.pageX : event.pageX;
var pageY = event.gesture ? event.gesture.center.pageY : event.pageY;
var x = pageX - util.getAbsoluteLeft(this.dom.centerContainer);
var y = pageY - util.getAbsoluteTop(this.dom.centerContainer);
var snap = this.itemSet.options.snap || null;
var scale = this.body.util.getScale();
var step = this.body.util.getStep();
var time = this._toTime(x);
var snappedTime = snap ? snap(time, scale, step) : time;
var element = util.getTarget(event);
var what = null;
if (item != null) {what = 'item';}
else if (util.hasParent(element, this.dom.center)) {what = 'background';}
else if (util.hasParent(element, this.timeAxis.dom.foreground)) {what = 'axis';}
else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) {what = 'axis';}
else if (util.hasParent(element, this.itemSet.dom.labelSet)) {what = 'group-label';}
else if (util.hasParent(element, this.customTime.bar)) {what = 'custom-time';}
else if (util.hasParent(element, this.currentTime.bar)) {what = 'current-time';}
return {
event: event,
item: item ? item.id : null,
group: group ? group.groupId : null,
what: what,
pageX: pageX,
pageY: pageY,
x: x,
y: y,
time: time,
snappedTime: snappedTime
}
};
module.exports = Timeline; module.exports = Timeline;
@ -13466,7 +13537,7 @@ return /******/ (function(modules) { // webpackBootstrap
*/ */
ItemSet.prototype._onTouch = function (event) { ItemSet.prototype._onTouch = function (event) {
// store the touched item, used in _onDragStart // store the touched item, used in _onDragStart
this.touchParams.item = ItemSet.itemFromTarget(event);
this.touchParams.item = this.itemFromTarget(event);
}; };
/** /**
@ -13791,7 +13862,7 @@ return /******/ (function(modules) { // webpackBootstrap
var oldSelection = this.getSelection(); var oldSelection = this.getSelection();
var item = ItemSet.itemFromTarget(event);
var item = this.itemFromTarget(event);
var selection = item ? [item.id] : []; var selection = item ? [item.id] : [];
this.setSelection(selection); this.setSelection(selection);
@ -13817,7 +13888,7 @@ return /******/ (function(modules) { // webpackBootstrap
var me = this, var me = this,
snap = this.options.snap || null, snap = this.options.snap || null,
item = ItemSet.itemFromTarget(event);
item = this.itemFromTarget(event);
if (item) { if (item) {
// update item // update item
@ -13875,7 +13946,7 @@ return /******/ (function(modules) { // webpackBootstrap
if (!this.options.selectable) return; if (!this.options.selectable) return;
var selection, var selection,
item = ItemSet.itemFromTarget(event);
item = this.itemFromTarget(event);
if (item) { if (item) {
// multi select items // multi select items
@ -13963,7 +14034,7 @@ return /******/ (function(modules) { // webpackBootstrap
* @param {Event} event * @param {Event} event
* @return {Item | null} item * @return {Item | null} item
*/ */
ItemSet.itemFromTarget = function(event) {
ItemSet.prototype.itemFromTarget = function(event) {
var target = event.target; var target = event.target;
while (target) { while (target) {
if (target.hasOwnProperty('timeline-item')) { if (target.hasOwnProperty('timeline-item')) {
@ -13982,17 +14053,7 @@ return /******/ (function(modules) { // webpackBootstrap
* @return {Group | null} group * @return {Group | null} group
*/ */
ItemSet.prototype.groupFromTarget = function(event) { ItemSet.prototype.groupFromTarget = function(event) {
// TODO: cleanup when the new solution is stable (also on mobile)
//var target = event.target;
//while (target) {
// if (target.hasOwnProperty('timeline-group')) {
// return target['timeline-group'];
// }
// target = target.parentNode;
//}
//
var clientY = event.gesture.center.clientY;
var clientY = event.gesture ? event.gesture.center.clientY : event.clientY;
for (var i = 0; i < this.groupIds.length; i++) { for (var i = 0; i < this.groupIds.length; i++) {
var groupId = this.groupIds[i]; var groupId = this.groupIds[i];
var group = this.groups[groupId]; var group = this.groups[groupId];
@ -15705,7 +15766,7 @@ return /******/ (function(modules) { // webpackBootstrap
var Emitter = __webpack_require__(56); var Emitter = __webpack_require__(56);
var Hammer = __webpack_require__(45); var Hammer = __webpack_require__(45);
var keycharm = __webpack_require__(57);
var keycharm = __webpack_require__(59);
var util = __webpack_require__(1); var util = __webpack_require__(1);
var hammerUtil = __webpack_require__(47); var hammerUtil = __webpack_require__(47);
var DataSet = __webpack_require__(3); var DataSet = __webpack_require__(3);
@ -22577,7 +22638,7 @@ return /******/ (function(modules) { // webpackBootstrap
// first check if moment.js is already loaded in the browser window, if so, // first check if moment.js is already loaded in the browser window, if so,
// use this instance. Else, load via commonjs. // use this instance. Else, load via commonjs.
module.exports = (typeof window !== 'undefined') && window['moment'] || __webpack_require__(58);
module.exports = (typeof window !== 'undefined') && window['moment'] || __webpack_require__(57);
/***/ }, /***/ },
@ -22587,7 +22648,7 @@ return /******/ (function(modules) { // webpackBootstrap
// Only load hammer.js when in a browser environment // Only load hammer.js when in a browser environment
// (loading hammer.js in a node.js environment gives errors) // (loading hammer.js in a node.js environment gives errors)
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
module.exports = window['Hammer'] || __webpack_require__(59);
module.exports = window['Hammer'] || __webpack_require__(58);
} }
else { else {
module.exports = function () { module.exports = function () {
@ -22629,7 +22690,7 @@ return /******/ (function(modules) { // webpackBootstrap
* top, bottom, content, and background panel. * top, bottom, content, and background panel.
* @param {Element} container The container element where the Core will * @param {Element} container The container element where the Core will
* be attached. * be attached.
* @private
* @protected
*/ */
Core.prototype._create = function (container) { Core.prototype._create = function (container) {
this.dom = {}; this.dom = {};
@ -22657,7 +22718,7 @@ return /******/ (function(modules) { // webpackBootstrap
this.dom.background.className = 'vispanel background'; this.dom.background.className = 'vispanel background';
this.dom.backgroundVertical.className = 'vispanel background vertical'; this.dom.backgroundVertical.className = 'vispanel background vertical';
this.dom.backgroundHorizontal.className = 'vispanel background horizontal'; this.dom.backgroundHorizontal.className = 'vispanel background horizontal';
this.dom.centerContainer.className = 'vispanel center';
this.dom.centerContainer.className = 'vispanel center jooo';
this.dom.leftContainer.className = 'vispanel left'; this.dom.leftContainer.className = 'vispanel left';
this.dom.rightContainer.className = 'vispanel right'; this.dom.rightContainer.className = 'vispanel right';
this.dom.top.className = 'vispanel top'; this.dom.top.className = 'vispanel top';
@ -23389,7 +23450,7 @@ return /******/ (function(modules) { // webpackBootstrap
* Convert a position on screen (pixels) to a datetime * Convert a position on screen (pixels) to a datetime
* @param {int} x Position on the screen in pixels * @param {int} x Position on the screen in pixels
* @return {Date} time The datetime the corresponds with given position x * @return {Date} time The datetime the corresponds with given position x
* @private
* @protected
*/ */
// TODO: move this function to Range // TODO: move this function to Range
Core.prototype._toTime = function(x) { Core.prototype._toTime = function(x) {
@ -23400,7 +23461,7 @@ return /******/ (function(modules) { // webpackBootstrap
* Convert a position on the global screen (pixels) to a datetime * Convert a position on the global screen (pixels) to a datetime
* @param {int} x Position on the screen in pixels * @param {int} x Position on the screen in pixels
* @return {Date} time The datetime the corresponds with given position x * @return {Date} time The datetime the corresponds with given position x
* @private
* @protected
*/ */
// TODO: move this function to Range // TODO: move this function to Range
Core.prototype._toGlobalTime = function(x) { Core.prototype._toGlobalTime = function(x) {
@ -23414,7 +23475,7 @@ return /******/ (function(modules) { // webpackBootstrap
* @param {Date} time A date * @param {Date} time A date
* @return {int} x The position on the screen in pixels which corresponds * @return {int} x The position on the screen in pixels which corresponds
* with the given date. * with the given date.
* @private
* @protected
*/ */
// TODO: move this function to Range // TODO: move this function to Range
Core.prototype._toScreen = function(time) { Core.prototype._toScreen = function(time) {
@ -23429,7 +23490,7 @@ return /******/ (function(modules) { // webpackBootstrap
* @param {Date} time A date * @param {Date} time A date
* @return {int} x The position on root in pixels which corresponds * @return {int} x The position on root in pixels which corresponds
* with the given date. * with the given date.
* @private
* @protected
*/ */
// TODO: move this function to Range // TODO: move this function to Range
Core.prototype._toGlobalScreen = function(time) { Core.prototype._toGlobalScreen = function(time) {
@ -24371,7 +24432,7 @@ return /******/ (function(modules) { // webpackBootstrap
/* 53 */ /* 53 */
/***/ function(module, exports, __webpack_require__) { /***/ function(module, exports, __webpack_require__) {
var keycharm = __webpack_require__(57);
var keycharm = __webpack_require__(59);
var Emitter = __webpack_require__(56); var Emitter = __webpack_require__(56);
var Hammer = __webpack_require__(45); var Hammer = __webpack_require__(45);
var util = __webpack_require__(1); var util = __webpack_require__(1);
@ -24968,205 +25029,6 @@ return /******/ (function(modules) { // webpackBootstrap
/***/ }, /***/ },
/* 57 */ /* 57 */
/***/ function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict";
/**
* Created by Alex on 11/6/2014.
*/
// https://github.com/umdjs/umd/blob/master/returnExports.js#L40-L60
// if the module has no dependencies, the above pattern can be simplified to
(function (root, factory) {
if (true) {
// AMD. Register as an anonymous module.
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.keycharm = factory();
}
}(this, function () {
function keycharm(options) {
var preventDefault = options && options.preventDefault || false;
var container = options && options.container || window;
var _exportFunctions = {};
var _bound = {keydown:{}, keyup:{}};
var _keys = {};
var i;
// a - z
for (i = 97; i <= 122; i++) {_keys[String.fromCharCode(i)] = {code:65 + (i - 97), shift: false};}
// A - Z
for (i = 65; i <= 90; i++) {_keys[String.fromCharCode(i)] = {code:i, shift: true};}
// 0 - 9
for (i = 0; i <= 9; i++) {_keys['' + i] = {code:48 + i, shift: false};}
// F1 - F12
for (i = 1; i <= 12; i++) {_keys['F' + i] = {code:111 + i, shift: false};}
// num0 - num9
for (i = 0; i <= 9; i++) {_keys['num' + i] = {code:96 + i, shift: false};}
// numpad misc
_keys['num*'] = {code:106, shift: false};
_keys['num+'] = {code:107, shift: false};
_keys['num-'] = {code:109, shift: false};
_keys['num/'] = {code:111, shift: false};
_keys['num.'] = {code:110, shift: false};
// arrows
_keys['left'] = {code:37, shift: false};
_keys['up'] = {code:38, shift: false};
_keys['right'] = {code:39, shift: false};
_keys['down'] = {code:40, shift: false};
// extra keys
_keys['space'] = {code:32, shift: false};
_keys['enter'] = {code:13, shift: false};
_keys['shift'] = {code:16, shift: undefined};
_keys['esc'] = {code:27, shift: false};
_keys['backspace'] = {code:8, shift: false};
_keys['tab'] = {code:9, shift: false};
_keys['ctrl'] = {code:17, shift: false};
_keys['alt'] = {code:18, shift: false};
_keys['delete'] = {code:46, shift: false};
_keys['pageup'] = {code:33, shift: false};
_keys['pagedown'] = {code:34, shift: false};
// symbols
_keys['='] = {code:187, shift: false};
_keys['-'] = {code:189, shift: false};
_keys[']'] = {code:221, shift: false};
_keys['['] = {code:219, shift: false};
var down = function(event) {handleEvent(event,'keydown');};
var up = function(event) {handleEvent(event,'keyup');};
// handle the actualy bound key with the event
var handleEvent = function(event,type) {
if (_bound[type][event.keyCode] !== undefined) {
var bound = _bound[type][event.keyCode];
for (var i = 0; i < bound.length; i++) {
if (bound[i].shift === undefined) {
bound[i].fn(event);
}
else if (bound[i].shift == true && event.shiftKey == true) {
bound[i].fn(event);
}
else if (bound[i].shift == false && event.shiftKey == false) {
bound[i].fn(event);
}
}
if (preventDefault == true) {
event.preventDefault();
}
}
};
// bind a key to a callback
_exportFunctions.bind = function(key, callback, type) {
if (type === undefined) {
type = 'keydown';
}
if (_keys[key] === undefined) {
throw new Error("unsupported key: " + key);
}
if (_bound[type][_keys[key].code] === undefined) {
_bound[type][_keys[key].code] = [];
}
_bound[type][_keys[key].code].push({fn:callback, shift:_keys[key].shift});
};
// bind all keys to a call back (demo purposes)
_exportFunctions.bindAll = function(callback, type) {
if (type === undefined) {
type = 'keydown';
}
for (var key in _keys) {
if (_keys.hasOwnProperty(key)) {
_exportFunctions.bind(key,callback,type);
}
}
};
// get the key label from an event
_exportFunctions.getKey = function(event) {
for (var key in _keys) {
if (_keys.hasOwnProperty(key)) {
if (event.shiftKey == true && _keys[key].shift == true && event.keyCode == _keys[key].code) {
return key;
}
else if (event.shiftKey == false && _keys[key].shift == false && event.keyCode == _keys[key].code) {
return key;
}
else if (event.keyCode == _keys[key].code && key == 'shift') {
return key;
}
}
}
return "unknown key, currently not supported";
};
// unbind either a specific callback from a key or all of them (by leaving callback undefined)
_exportFunctions.unbind = function(key, callback, type) {
if (type === undefined) {
type = 'keydown';
}
if (_keys[key] === undefined) {
throw new Error("unsupported key: " + key);
}
if (callback !== undefined) {
var newBindings = [];
var bound = _bound[type][_keys[key].code];
if (bound !== undefined) {
for (var i = 0; i < bound.length; i++) {
if (!(bound[i].fn == callback && bound[i].shift == _keys[key].shift)) {
newBindings.push(_bound[type][_keys[key].code][i]);
}
}
}
_bound[type][_keys[key].code] = newBindings;
}
else {
_bound[type][_keys[key].code] = [];
}
};
// reset all bound variables.
_exportFunctions.reset = function() {
_bound = {keydown:{}, keyup:{}};
};
// unbind all listeners and reset all variables.
_exportFunctions.destroy = function() {
_bound = {keydown:{}, keyup:{}};
container.removeEventListener('keydown', down, true);
container.removeEventListener('keyup', up, true);
};
// create listeners.
container.addEventListener('keydown',down,true);
container.addEventListener('keyup',up,true);
// return the public functions.
return _exportFunctions;
}
return keycharm;
}));
/***/ },
/* 58 */
/***/ function(module, exports, __webpack_require__) { /***/ function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(global, module) {//! moment.js var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(global, module) {//! moment.js
@ -28216,7 +28078,7 @@ return /******/ (function(modules) { // webpackBootstrap
/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(71)(module))) /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(71)(module)))
/***/ }, /***/ },
/* 59 */
/* 58 */
/***/ function(module, exports, __webpack_require__) { /***/ function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v1.1.3 - 2014-05-20 var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v1.1.3 - 2014-05-20
@ -30382,6 +30244,205 @@ return /******/ (function(modules) { // webpackBootstrap
})(window); })(window);
/***/ },
/* 59 */
/***/ function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict";
/**
* Created by Alex on 11/6/2014.
*/
// https://github.com/umdjs/umd/blob/master/returnExports.js#L40-L60
// if the module has no dependencies, the above pattern can be simplified to
(function (root, factory) {
if (true) {
// AMD. Register as an anonymous module.
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.keycharm = factory();
}
}(this, function () {
function keycharm(options) {
var preventDefault = options && options.preventDefault || false;
var container = options && options.container || window;
var _exportFunctions = {};
var _bound = {keydown:{}, keyup:{}};
var _keys = {};
var i;
// a - z
for (i = 97; i <= 122; i++) {_keys[String.fromCharCode(i)] = {code:65 + (i - 97), shift: false};}
// A - Z
for (i = 65; i <= 90; i++) {_keys[String.fromCharCode(i)] = {code:i, shift: true};}
// 0 - 9
for (i = 0; i <= 9; i++) {_keys['' + i] = {code:48 + i, shift: false};}
// F1 - F12
for (i = 1; i <= 12; i++) {_keys['F' + i] = {code:111 + i, shift: false};}
// num0 - num9
for (i = 0; i <= 9; i++) {_keys['num' + i] = {code:96 + i, shift: false};}
// numpad misc
_keys['num*'] = {code:106, shift: false};
_keys['num+'] = {code:107, shift: false};
_keys['num-'] = {code:109, shift: false};
_keys['num/'] = {code:111, shift: false};
_keys['num.'] = {code:110, shift: false};
// arrows
_keys['left'] = {code:37, shift: false};
_keys['up'] = {code:38, shift: false};
_keys['right'] = {code:39, shift: false};
_keys['down'] = {code:40, shift: false};
// extra keys
_keys['space'] = {code:32, shift: false};
_keys['enter'] = {code:13, shift: false};
_keys['shift'] = {code:16, shift: undefined};
_keys['esc'] = {code:27, shift: false};
_keys['backspace'] = {code:8, shift: false};
_keys['tab'] = {code:9, shift: false};
_keys['ctrl'] = {code:17, shift: false};
_keys['alt'] = {code:18, shift: false};
_keys['delete'] = {code:46, shift: false};
_keys['pageup'] = {code:33, shift: false};
_keys['pagedown'] = {code:34, shift: false};
// symbols
_keys['='] = {code:187, shift: false};
_keys['-'] = {code:189, shift: false};
_keys[']'] = {code:221, shift: false};
_keys['['] = {code:219, shift: false};
var down = function(event) {handleEvent(event,'keydown');};
var up = function(event) {handleEvent(event,'keyup');};
// handle the actualy bound key with the event
var handleEvent = function(event,type) {
if (_bound[type][event.keyCode] !== undefined) {
var bound = _bound[type][event.keyCode];
for (var i = 0; i < bound.length; i++) {
if (bound[i].shift === undefined) {
bound[i].fn(event);
}
else if (bound[i].shift == true && event.shiftKey == true) {
bound[i].fn(event);
}
else if (bound[i].shift == false && event.shiftKey == false) {
bound[i].fn(event);
}
}
if (preventDefault == true) {
event.preventDefault();
}
}
};
// bind a key to a callback
_exportFunctions.bind = function(key, callback, type) {
if (type === undefined) {
type = 'keydown';
}
if (_keys[key] === undefined) {
throw new Error("unsupported key: " + key);
}
if (_bound[type][_keys[key].code] === undefined) {
_bound[type][_keys[key].code] = [];
}
_bound[type][_keys[key].code].push({fn:callback, shift:_keys[key].shift});
};
// bind all keys to a call back (demo purposes)
_exportFunctions.bindAll = function(callback, type) {
if (type === undefined) {
type = 'keydown';
}
for (var key in _keys) {
if (_keys.hasOwnProperty(key)) {
_exportFunctions.bind(key,callback,type);
}
}
};
// get the key label from an event
_exportFunctions.getKey = function(event) {
for (var key in _keys) {
if (_keys.hasOwnProperty(key)) {
if (event.shiftKey == true && _keys[key].shift == true && event.keyCode == _keys[key].code) {
return key;
}
else if (event.shiftKey == false && _keys[key].shift == false && event.keyCode == _keys[key].code) {
return key;
}
else if (event.keyCode == _keys[key].code && key == 'shift') {
return key;
}
}
}
return "unknown key, currently not supported";
};
// unbind either a specific callback from a key or all of them (by leaving callback undefined)
_exportFunctions.unbind = function(key, callback, type) {
if (type === undefined) {
type = 'keydown';
}
if (_keys[key] === undefined) {
throw new Error("unsupported key: " + key);
}
if (callback !== undefined) {
var newBindings = [];
var bound = _bound[type][_keys[key].code];
if (bound !== undefined) {
for (var i = 0; i < bound.length; i++) {
if (!(bound[i].fn == callback && bound[i].shift == _keys[key].shift)) {
newBindings.push(_bound[type][_keys[key].code][i]);
}
}
}
_bound[type][_keys[key].code] = newBindings;
}
else {
_bound[type][_keys[key].code] = [];
}
};
// reset all bound variables.
_exportFunctions.reset = function() {
_bound = {keydown:{}, keyup:{}};
};
// unbind all listeners and reset all variables.
_exportFunctions.destroy = function() {
_bound = {keydown:{}, keyup:{}};
container.removeEventListener('keydown', down, true);
container.removeEventListener('keyup', up, true);
};
// create listeners.
container.addEventListener('keydown',down,true);
container.addEventListener('keyup',up,true);
// return the public functions.
return _exportFunctions;
}
return keycharm;
}));
/***/ }, /***/ },
/* 60 */ /* 60 */
/***/ function(module, exports, __webpack_require__) { /***/ function(module, exports, __webpack_require__) {

+ 1
- 1
dist/vis.map
File diff suppressed because it is too large
View File


+ 14
- 14
dist/vis.min.js
File diff suppressed because it is too large
View File


+ 6
- 6
docs/network.html View File

@ -165,14 +165,14 @@ The constructor accepts three parameters:
<code>edges</code>, which both contain an array with objects. <code>edges</code>, which both contain an array with objects.
Optionally, data may contain an <code>options</code> object. Optionally, data may contain an <code>options</code> object.
The parameter <code>data</code> is optional, data can also be set using The parameter <code>data</code> is optional, data can also be set using
the method <code>setData</code>. Section <a href="#Data_Format">Data Format</a>
the method <code>setData</code>. Section <a href="#Data_format">Data Format</a>
describes the data object. describes the data object.
</li> </li>
<li> <li>
<code>options</code> is an optional Object containing a name-value map <code>options</code> is an optional Object containing a name-value map
with options. Options can also be set using the method with options. Options can also be set using the method
<code>setOptions</code>. <code>setOptions</code>.
Section <a href="#Configuration_Options">Configuration Options</a>
Section <a href="#Configuration_options">Configuration Options</a>
describes the available options. describes the available options.
</li> </li>
</ul> </ul>
@ -214,7 +214,7 @@ var data = {
<span style="font-weight: bold;">A property <code>options</code></span>, <span style="font-weight: bold;">A property <code>options</code></span>,
containing an object with global options. containing an object with global options.
Options can be provided as third parameter in the network constructor Options can be provided as third parameter in the network constructor
as well. Section <a href="#Configuration_Options">Configuration Options</a>
as well. Section <a href="#Configuration_options">Configuration Options</a>
describes the available options. describes the available options.
</li> </li>
@ -647,7 +647,7 @@ var options = {
</tr> </tr>
<tr> <tr>
<td>freezeForStabilization</a></td>
<td>freezeForStabilization</td>
<td>Boolean</td> <td>Boolean</td>
<td>false</td> <td>false</td>
<td> <td>
@ -972,7 +972,7 @@ mySize = minSize + diff * scale;
<td>When using values, you can let the font scale with the size of the nodes if you enable the scaleFontWithValue option. This is the minimum value of the fontSize.</td> <td>When using values, you can let the font scale with the size of the nodes if you enable the scaleFontWithValue option. This is the minimum value of the fontSize.</td>
</tr> </tr>
<tr> <tr>
<td></td>fontSizeMax</td>
<td>fontSizeMax</td>
<td>Number</td> <td>Number</td>
<td>30</td> <td>30</td>
<td>When using values, you can let the font scale with the size of the nodes if you enable the scaleFontWithValue option. This is the maximum value of the fontSize.</td> <td>When using values, you can let the font scale with the size of the nodes if you enable the scaleFontWithValue option. This is the maximum value of the fontSize.</td>
@ -1483,7 +1483,7 @@ To unify the physics system, the damping, repulsion distance and edge length hav
If no options for the physics system are supplied, the Barnes-Hut method will be used with the default parameters. If you want to customize the physics system easily, you can use the configurePhysics option. <br/> If no options for the physics system are supplied, the Barnes-Hut method will be used with the default parameters. If you want to customize the physics system easily, you can use the configurePhysics option. <br/>
When using the hierarchical display option, hierarchicalRepulsion is automatically used as the physics solver. Similarly, if you use the hierarchicalRepulsion physics option, hierarchical display is automatically turned on with default settings. When using the hierarchical display option, hierarchicalRepulsion is automatically used as the physics solver. Similarly, if you use the hierarchicalRepulsion physics option, hierarchical display is automatically turned on with default settings.
<p class="important_note">Note: if the behaviour of your network is not the way you want it, use configurePhysics as described <u><a href="#PhysicsConfiguration">below</a></u> or by <u><a href="../examples/network/25_physics_configuration.html">example 25</a></u>.</p>
<p class="important_note">Note: if the behaviour of your network is not the way you want it, use configurePhysics as described <u><a href="#PhysicsConfiguration">below</a></u> or by <u><a href="../examples/network/25_physics_configuration.html">example 25</a></u>.
</p> </p>
<pre class="prettyprint"> <pre class="prettyprint">
// These variables must be defined in an options object named physics. // These variables must be defined in an options object named physics.

+ 63
- 10
docs/timeline.html View File

@ -919,6 +919,26 @@ timeline.clear({options: true}); // clear options only
</td> </td>
</tr> </tr>
<tr id="getEventProperties">
<td>getEventProperties(event)</td>
<td>Object</td>
<td>
Returns an Object with relevant properties from an event:
<ul>
<li><code>group</code> (Number | null): the id of the clicked group.</li>
<li><code>item</code> (Number | null): the id of the clicked item.</li>
<li><code>pageX</code> (Number): absolute horizontal position of the click event.</li>
<li><code>pageY</code> (Number): absolute vertical position of the click event.</li>
<li><code>x</code> (Number): relative horizontal position of the click event.</li>
<li><code>y</code> (Number): relative vertical position of the click event.</li>
<li><code>time</code> (Date): Date of the clicked event.</li>
<li><code>snappedTime</code> (Date): Date of the clicked event, snapped to a nice value.</li>
<li><code>what</code> (String | null): name of the clicked thing: <code>item</code>, <code>background</code>, <code>axis</code>, <code>group-label</code>, <code>custom-time</code>, or <code>current-time</code>.</li>
<li><code>event</code> (Object): the original click event.</li>
</ul>
</td>
</tr>
<tr> <tr>
<td>getSelection()</td> <td>getSelection()</td>
<td>Number[]</td> <td>Number[]</td>
@ -1093,16 +1113,49 @@ timeline.off('select', onSelect);
<th>Description</th> <th>Description</th>
<th>Properties</th> <th>Properties</th>
</tr> </tr>
<tr>
<td>finishedRedraw</td>
<td>Fired after a redraw is complete. When moving the timeline around, this could be fired frequently.
</td>
<td>
none.
</td>
</tr>
<tr>
<tr>
<td>click</td>
<td>Fired when clicked inside the Timeline.
</td>
<td>
Passes a properties object as returned by the method <a href="#getEventProperties"><code>Timeline.getEventProperties(event)</code></a>.
</td>
</tr>
<tr>
<td>contextmenu</td>
<td>Fired when right-clicked inside the Timeline. Note that in order to prevent the context menu from showing up, default behavior of the event must be stopped:
<pre class="prettyprint lang-js">timeline.on('contextmenu', function (props) {
alert('Right click!');
props.event.preventDefault();
});
</pre>
</td>
<td>
Passes a properties object as returned by the method <a href="#getEventProperties"><code>Timeline.getEventProperties(event)</code></a>.
</td>
</tr>
<tr>
<td>doubleClick</td>
<td>Fired when double clicked inside the Timeline.
</td>
<td>
Passes a properties object as returned by the method <a href="#getEventProperties"><code>Timeline.getEventProperties(event)</code></a>.
</td>
</tr>
<tr>
<td>finishedRedraw</td>
<td>Fired after a redraw is complete. When moving the timeline around, this could be fired frequently.
</td>
<td>
none.
</td>
</tr>
<tr>
<td>rangechange</td> <td>rangechange</td>
<td>Fired repeatedly when the timeline window is being changed. <td>Fired repeatedly when the timeline window is being changed.
</td> </td>

+ 6
- 6
lib/timeline/Core.js View File

@ -27,7 +27,7 @@ Emitter(Core.prototype);
* top, bottom, content, and background panel. * top, bottom, content, and background panel.
* @param {Element} container The container element where the Core will * @param {Element} container The container element where the Core will
* be attached. * be attached.
* @private
* @protected
*/ */
Core.prototype._create = function (container) { Core.prototype._create = function (container) {
this.dom = {}; this.dom = {};
@ -55,7 +55,7 @@ Core.prototype._create = function (container) {
this.dom.background.className = 'vispanel background'; this.dom.background.className = 'vispanel background';
this.dom.backgroundVertical.className = 'vispanel background vertical'; this.dom.backgroundVertical.className = 'vispanel background vertical';
this.dom.backgroundHorizontal.className = 'vispanel background horizontal'; this.dom.backgroundHorizontal.className = 'vispanel background horizontal';
this.dom.centerContainer.className = 'vispanel center';
this.dom.centerContainer.className = 'vispanel center jooo';
this.dom.leftContainer.className = 'vispanel left'; this.dom.leftContainer.className = 'vispanel left';
this.dom.rightContainer.className = 'vispanel right'; this.dom.rightContainer.className = 'vispanel right';
this.dom.top.className = 'vispanel top'; this.dom.top.className = 'vispanel top';
@ -787,7 +787,7 @@ Core.prototype.getCurrentTime = function() {
* Convert a position on screen (pixels) to a datetime * Convert a position on screen (pixels) to a datetime
* @param {int} x Position on the screen in pixels * @param {int} x Position on the screen in pixels
* @return {Date} time The datetime the corresponds with given position x * @return {Date} time The datetime the corresponds with given position x
* @private
* @protected
*/ */
// TODO: move this function to Range // TODO: move this function to Range
Core.prototype._toTime = function(x) { Core.prototype._toTime = function(x) {
@ -798,7 +798,7 @@ Core.prototype._toTime = function(x) {
* Convert a position on the global screen (pixels) to a datetime * Convert a position on the global screen (pixels) to a datetime
* @param {int} x Position on the screen in pixels * @param {int} x Position on the screen in pixels
* @return {Date} time The datetime the corresponds with given position x * @return {Date} time The datetime the corresponds with given position x
* @private
* @protected
*/ */
// TODO: move this function to Range // TODO: move this function to Range
Core.prototype._toGlobalTime = function(x) { Core.prototype._toGlobalTime = function(x) {
@ -812,7 +812,7 @@ Core.prototype._toGlobalTime = function(x) {
* @param {Date} time A date * @param {Date} time A date
* @return {int} x The position on the screen in pixels which corresponds * @return {int} x The position on the screen in pixels which corresponds
* with the given date. * with the given date.
* @private
* @protected
*/ */
// TODO: move this function to Range // TODO: move this function to Range
Core.prototype._toScreen = function(time) { Core.prototype._toScreen = function(time) {
@ -827,7 +827,7 @@ Core.prototype._toScreen = function(time) {
* @param {Date} time A date * @param {Date} time A date
* @return {int} x The position on root in pixels which corresponds * @return {int} x The position on root in pixels which corresponds
* with the given date. * with the given date.
* @private
* @protected
*/ */
// TODO: move this function to Range // TODO: move this function to Range
Core.prototype._toGlobalScreen = function(time) { Core.prototype._toGlobalScreen = function(time) {

+ 53
- 0
lib/timeline/Timeline.js View File

@ -102,6 +102,16 @@ function Timeline (container, items, groups, options) {
this.itemsData = null; // DataSet this.itemsData = null; // DataSet
this.groupsData = null; // DataSet this.groupsData = null; // DataSet
this.on('tap', function (event) {
me.emit('click', me.getEventProperties(event))
});
this.on('doubletap', function (event) {
me.emit('doubleClick', me.getEventProperties(event))
});
this.dom.root.oncontextmenu = function (event) {
me.emit('contextmenu', me.getEventProperties(event))
};
// apply options // apply options
if (options) { if (options) {
this.setOptions(options); this.setOptions(options);
@ -326,5 +336,48 @@ Timeline.prototype.getItemRange = function() {
}; };
}; };
/**
* Generate Timeline related information from an event
* @param {Event} event
* @return {Object} An object with related information, like on which area
* The event happened, whether clicked on an item, etc.
*/
Timeline.prototype.getEventProperties = function (event) {
var item = this.itemSet.itemFromTarget(event);
var group = this.itemSet.groupFromTarget(event);
var pageX = event.gesture ? event.gesture.center.pageX : event.pageX;
var pageY = event.gesture ? event.gesture.center.pageY : event.pageY;
var x = pageX - util.getAbsoluteLeft(this.dom.centerContainer);
var y = pageY - util.getAbsoluteTop(this.dom.centerContainer);
var snap = this.itemSet.options.snap || null;
var scale = this.body.util.getScale();
var step = this.body.util.getStep();
var time = this._toTime(x);
var snappedTime = snap ? snap(time, scale, step) : time;
var element = util.getTarget(event);
var what = null;
if (item != null) {what = 'item';}
else if (util.hasParent(element, this.dom.center)) {what = 'background';}
else if (util.hasParent(element, this.timeAxis.dom.foreground)) {what = 'axis';}
else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) {what = 'axis';}
else if (util.hasParent(element, this.itemSet.dom.labelSet)) {what = 'group-label';}
else if (util.hasParent(element, this.customTime.bar)) {what = 'custom-time';}
else if (util.hasParent(element, this.currentTime.bar)) {what = 'current-time';}
return {
event: event,
item: item ? item.id : null,
group: group ? group.groupId : null,
what: what,
pageX: pageX,
pageY: pageY,
x: x,
y: y,
time: time,
snappedTime: snappedTime
}
};
module.exports = Timeline; module.exports = Timeline;

+ 6
- 16
lib/timeline/component/ItemSet.js View File

@ -1082,7 +1082,7 @@ ItemSet.prototype._constructByEndArray = function(array) {
*/ */
ItemSet.prototype._onTouch = function (event) { ItemSet.prototype._onTouch = function (event) {
// store the touched item, used in _onDragStart // store the touched item, used in _onDragStart
this.touchParams.item = ItemSet.itemFromTarget(event);
this.touchParams.item = this.itemFromTarget(event);
}; };
/** /**
@ -1407,7 +1407,7 @@ ItemSet.prototype._onSelectItem = function (event) {
var oldSelection = this.getSelection(); var oldSelection = this.getSelection();
var item = ItemSet.itemFromTarget(event);
var item = this.itemFromTarget(event);
var selection = item ? [item.id] : []; var selection = item ? [item.id] : [];
this.setSelection(selection); this.setSelection(selection);
@ -1433,7 +1433,7 @@ ItemSet.prototype._onAddItem = function (event) {
var me = this, var me = this,
snap = this.options.snap || null, snap = this.options.snap || null,
item = ItemSet.itemFromTarget(event);
item = this.itemFromTarget(event);
if (item) { if (item) {
// update item // update item
@ -1491,7 +1491,7 @@ ItemSet.prototype._onMultiSelectItem = function (event) {
if (!this.options.selectable) return; if (!this.options.selectable) return;
var selection, var selection,
item = ItemSet.itemFromTarget(event);
item = this.itemFromTarget(event);
if (item) { if (item) {
// multi select items // multi select items
@ -1579,7 +1579,7 @@ ItemSet._getItemRange = function(itemsData) {
* @param {Event} event * @param {Event} event
* @return {Item | null} item * @return {Item | null} item
*/ */
ItemSet.itemFromTarget = function(event) {
ItemSet.prototype.itemFromTarget = function(event) {
var target = event.target; var target = event.target;
while (target) { while (target) {
if (target.hasOwnProperty('timeline-item')) { if (target.hasOwnProperty('timeline-item')) {
@ -1598,17 +1598,7 @@ ItemSet.itemFromTarget = function(event) {
* @return {Group | null} group * @return {Group | null} group
*/ */
ItemSet.prototype.groupFromTarget = function(event) { ItemSet.prototype.groupFromTarget = function(event) {
// TODO: cleanup when the new solution is stable (also on mobile)
//var target = event.target;
//while (target) {
// if (target.hasOwnProperty('timeline-group')) {
// return target['timeline-group'];
// }
// target = target.parentNode;
//}
//
var clientY = event.gesture.center.clientY;
var clientY = event.gesture ? event.gesture.center.clientY : event.clientY;
for (var i = 0; i < this.groupIds.length; i++) { for (var i = 0; i < this.groupIds.length; i++) {
var groupId = this.groupIds[i]; var groupId = this.groupIds[i];
var group = this.groups[groupId]; var group = this.groups[groupId];

+ 18
- 0
lib/util.js View File

@ -665,6 +665,24 @@ exports.getTarget = function(event) {
return target; return target;
}; };
/**
* Check if given element contains given parent somewhere in the DOM tree
* @param {Element} element
* @param {Element} parent
*/
exports.hasParent = function (element, parent) {
var e = element;
while (e) {
if (e === parent) {
return true;
}
e = e.parentNode;
}
return false;
};
exports.option = {}; exports.option = {};
/** /**

+ 10
- 2
test/timeline.html View File

@ -26,12 +26,12 @@
#visualization .grid.vertical.saturday, #visualization .grid.vertical.saturday,
#visualization .grid.vertical.sunday { #visualization .grid.vertical.sunday {
background: gray;
background: #ffef9f;
} }
#visualization .text.saturday, #visualization .text.saturday,
#visualization .text.sunday { #visualization .text.sunday {
color: white;
color: #ff7c02;
} }
</style> </style>
@ -140,6 +140,14 @@
console.log('select', selection); console.log('select', selection);
}); });
timeline.on('click', function (props) {
console.log('click', props);
});
timeline.on('contextmenu', function (props) {
console.log('contextmenu', props);
});
/* /*
timeline.on('rangechange', function (range) { timeline.on('rangechange', function (range) {
console.log('rangechange', range); console.log('rangechange', range);

+ 12
- 0
test/timeline_groups.html View File

@ -165,6 +165,18 @@
}); });
*/ */
timeline.on('click', function (props) {
console.log('click', props);
});
timeline.on('doubleClick', function (props) {
console.log('doubleClick', props);
});
timeline.on('contextmenu', function (props) {
console.log('contextmenu', props);
});
items.on('add', console.log.bind(console)); items.on('add', console.log.bind(console));
items.on('update', console.log.bind(console)); items.on('update', console.log.bind(console));
items.on('remove', console.log.bind(console)); items.on('remove', console.log.bind(console));

Loading…
Cancel
Save