UNPKG

vis-timeline

Version:

Create a fully customizable, interactive timeline with items and ranges.

331 lines (283 loc) 8.47 kB
import Hammer from '../../module/hammer'; import util from 'vis-util'; import Component from './Component'; import moment from '../../module/moment'; import locales from '../locales'; import './css/customtime.css'; /** A custom time bar */ class CustomTime extends Component { /** * @param {{range: Range, dom: Object}} body * @param {Object} [options] Available parameters: * {number | string} id * {string} locales * {string} locale * @constructor CustomTime * @extends Component */ constructor(body, options) { super() this.body = body; // default options this.defaultOptions = { moment, locales, locale: 'en', id: undefined, title: undefined }; this.options = util.extend({}, this.defaultOptions); if (options && options.time) { this.customTime = options.time; } else { this.customTime = new Date(); } this.eventParams = {}; // stores state parameters while dragging the bar this.setOptions(options); // create the DOM this._create(); } /** * Set options for the component. Options will be merged in current options. * @param {Object} options Available parameters: * {number | string} id * {string} locales * {string} locale */ setOptions(options) { if (options) { // copy all options that we know util.selectiveExtend(['moment', 'locale', 'locales', 'id', 'title', 'rtl'], this.options, options); } } /** * Create the DOM for the custom time * @private */ _create() { const bar = document.createElement('div'); bar['custom-time'] = this; bar.className = `vis-custom-time ${this.options.id || ''}`; bar.style.position = 'absolute'; bar.style.top = '0px'; bar.style.height = '100%'; this.bar = bar; const drag = document.createElement('div'); drag.style.position = 'relative'; drag.style.top = '0px'; this.options.rtl ? drag.style.right = '-10px' : drag.style.left = '-10px'; drag.style.height = '100%'; drag.style.width = '20px'; /** * * @param {WheelEvent} e */ function onMouseWheel (e) { this.body.range._onMouseWheel(e); } if (drag.addEventListener) { // IE9, Chrome, Safari, Opera drag.addEventListener("mousewheel", onMouseWheel.bind(this), false); // Firefox drag.addEventListener("DOMMouseScroll", onMouseWheel.bind(this), false); } else { // IE 6/7/8 drag.attachEvent("onmousewheel", onMouseWheel.bind(this)); } bar.appendChild(drag); // attach event listeners this.hammer = new Hammer(drag); this.hammer.on('panstart', this._onDragStart.bind(this)); this.hammer.on('panmove', this._onDrag.bind(this)); this.hammer.on('panend', this._onDragEnd.bind(this)); this.hammer.get('pan').set({threshold:5, direction: Hammer.DIRECTION_ALL}); } /** * Destroy the CustomTime bar */ destroy() { this.hide(); this.hammer.destroy(); this.hammer = null; this.body = null; } /** * Repaint the component * @return {boolean} Returns true if the component is resized */ redraw() { const parent = this.body.dom.backgroundVertical; if (this.bar.parentNode != parent) { // attach to the dom if (this.bar.parentNode) { this.bar.parentNode.removeChild(this.bar); } parent.appendChild(this.bar); } const x = this.body.util.toScreen(this.customTime); let locale = this.options.locales[this.options.locale]; if (!locale) { if (!this.warned) { console.warn(`WARNING: options.locales['${this.options.locale}'] not found. See https://visjs.github.io/vis-timeline/docs/timeline/#Localization`); this.warned = true; } locale = this.options.locales['en']; // fall back on english when not available } let title = this.options.title; // To hide the title completely use empty string ''. if (title === undefined) { title = `${locale.time}: ${this.options.moment(this.customTime).format('dddd, MMMM Do YYYY, H:mm:ss')}`; title = title.charAt(0).toUpperCase() + title.substring(1); } else if (typeof title === "function") { title = title.call(this.customTime); } this.options.rtl ? this.bar.style.right = `${x}px` : this.bar.style.left = `${x}px`; this.bar.title = title; return false; } /** * Remove the CustomTime from the DOM */ hide() { // remove the line from the DOM if (this.bar.parentNode) { this.bar.parentNode.removeChild(this.bar); } } /** * Set custom time. * @param {Date | number | string} time */ setCustomTime(time) { this.customTime = util.convert(time, 'Date'); this.redraw(); } /** * Retrieve the current custom time. * @return {Date} customTime */ getCustomTime() { return new Date(this.customTime.valueOf()); } /** * Set custom marker. * @param {string} [title] Title of the custom marker * @param {boolean} [editable] Make the custom marker editable. */ setCustomMarker(title, editable) { const marker = document.createElement('div'); marker.className = `vis-custom-time-marker`; marker.innerHTML = title; marker.style.position = 'absolute'; if (editable) { marker.setAttribute('contenteditable', 'true'); marker.addEventListener('pointerdown', function () { marker.focus(); }); marker.addEventListener('input', this._onMarkerChange.bind(this)); // The editable div element has no change event, so here emulates the change event. marker.title = title; marker.addEventListener('blur', function (event) { if (this.title != event.target.innerHTML) { this._onMarkerChanged(event); this.title = event.target.innerHTML; } }.bind(this)); } this.bar.appendChild(marker); } /** * Set custom title. * @param {Date | number | string} title */ setCustomTitle(title) { this.options.title = title; } /** * Start moving horizontally * @param {Event} event * @private */ _onDragStart(event) { this.eventParams.dragging = true; this.eventParams.customTime = this.customTime; event.stopPropagation(); } /** * Perform moving operating. * @param {Event} event * @private */ _onDrag(event) { if (!this.eventParams.dragging) return; let deltaX = this.options.rtl ? (-1) * event.deltaX : event.deltaX; const x = this.body.util.toScreen(this.eventParams.customTime) + deltaX; const time = this.body.util.toTime(x); this.setCustomTime(time); // fire a timechange event this.body.emitter.emit('timechange', { id: this.options.id, time: new Date(this.customTime.valueOf()), event }); event.stopPropagation(); } /** * Stop moving operating. * @param {Event} event * @private */ _onDragEnd(event) { if (!this.eventParams.dragging) return; // fire a timechanged event this.body.emitter.emit('timechanged', { id: this.options.id, time: new Date(this.customTime.valueOf()), event }); event.stopPropagation(); } /** * Perform input operating. * @param {Event} event * @private */ _onMarkerChange(event) { this.body.emitter.emit('markerchange', { id: this.options.id, title: event.target.innerHTML, event }); event.stopPropagation(); } /** * Perform change operating. * @param {Event} event * @private */ _onMarkerChanged(event) { this.body.emitter.emit('markerchanged', { id: this.options.id, title: event.target.innerHTML, event }); event.stopPropagation(); } /** * Find a custom time from an event target: * searches for the attribute 'custom-time' in the event target's element tree * @param {Event} event * @return {CustomTime | null} customTime */ static customTimeFromTarget(event) { let target = event.target; while (target) { if (target.hasOwnProperty('custom-time')) { return target['custom-time']; } target = target.parentNode; } return null; } } export default CustomTime;