Return-Path: X-Original-To: apmail-climate-commits-archive@minotaur.apache.org Delivered-To: apmail-climate-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 7A2C91123D for ; Tue, 1 Jul 2014 14:49:27 +0000 (UTC) Received: (qmail 19519 invoked by uid 500); 1 Jul 2014 14:49:27 -0000 Delivered-To: apmail-climate-commits-archive@climate.apache.org Received: (qmail 19421 invoked by uid 500); 1 Jul 2014 14:49:27 -0000 Mailing-List: contact commits-help@climate.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@climate.apache.org Delivered-To: mailing list commits@climate.apache.org Received: (qmail 19128 invoked by uid 99); 1 Jul 2014 14:49:26 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 01 Jul 2014 14:49:26 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id CA07E9914B1; Tue, 1 Jul 2014 14:49:26 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: joyce@apache.org To: commits@climate.apache.org Date: Tue, 01 Jul 2014 14:49:42 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [18/56] [partial] gh-pages clean up http://git-wip-us.apache.org/repos/asf/climate/blob/a53e3af5/ocw-ui/frontend/app/js/lib/timeline/timeline.js ---------------------------------------------------------------------- diff --git a/ocw-ui/frontend/app/js/lib/timeline/timeline.js b/ocw-ui/frontend/app/js/lib/timeline/timeline.js deleted file mode 100644 index 0e009a2..0000000 --- a/ocw-ui/frontend/app/js/lib/timeline/timeline.js +++ /dev/null @@ -1,6381 +0,0 @@ -/** - * @file timeline.js - * - * @brief - * The Timeline is an interactive visualization chart to visualize events in - * time, having a start and end date. - * You can freely move and zoom in the timeline by dragging - * and scrolling in the Timeline. Items are optionally dragable. The time - * scale on the axis is adjusted automatically, and supports scales ranging - * from milliseconds to years. - * - * Timeline is part of the CHAP Links library. - * - * Timeline is tested on Firefox 3.6, Safari 5.0, Chrome 6.0, Opera 10.6, and - * Internet Explorer 6+. - * - * @license - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - * - * Copyright (c) 2011-2013 Almende B.V. - * - * @author Jos de Jong, - * @date 2013-04-18 - * @version 2.4.2 - */ - -/* - * i18n mods by github user iktuz (https://gist.github.com/iktuz/3749287/) - * added to v2.4.1 with da_DK language by @bjarkebech - */ - -/* - * TODO - * - * Add zooming with pinching on Android - * - * Bug: when an item contains a javascript onclick or a link, this does not work - * when the item is not selected (when the item is being selected, - * it is redrawn, which cancels any onclick or link action) - * Bug: when an item contains an image without size, or a css max-width, it is not sized correctly - * Bug: neglect items when they have no valid start/end, instead of throwing an error - * Bug: Pinching on ipad does not work very well, sometimes the page will zoom when pinching vertically - * Bug: cannot set max width for an item, like div.timeline-event-content {white-space: normal; max-width: 100px;} - * Bug on IE in Quirks mode. When you have groups, and delete an item, the groups become invisible - */ - -/** - * Declare a unique namespace for CHAP's Common Hybrid Visualisation Library, - * "links" - */ -if (typeof links === 'undefined') { - links = {}; - // important: do not use var, as "var links = {};" will overwrite - // the existing links variable value with undefined in IE8, IE7. -} - - -/** - * Ensure the variable google exists - */ -if (typeof google === 'undefined') { - google = undefined; - // important: do not use var, as "var google = undefined;" will overwrite - // the existing google variable value with undefined in IE8, IE7. -} - - - -// Internet Explorer 8 and older does not support Array.indexOf, -// so we define it here in that case -// http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/ -if(!Array.prototype.indexOf) { - Array.prototype.indexOf = function(obj){ - for(var i = 0; i < this.length; i++){ - if(this[i] == obj){ - return i; - } - } - return -1; - } -} - -// Internet Explorer 8 and older does not support Array.forEach, -// so we define it here in that case -// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach -if (!Array.prototype.forEach) { - Array.prototype.forEach = function(fn, scope) { - for(var i = 0, len = this.length; i < len; ++i) { - fn.call(scope || this, this[i], i, this); - } - } -} - - -/** - * @constructor links.Timeline - * The timeline is a visualization chart to visualize events in time. - * - * The timeline is developed in javascript as a Google Visualization Chart. - * - * @param {Element} container The DOM element in which the Timeline will - * be created. Normally a div element. - */ -links.Timeline = function(container) { - if (!container) { - // this call was probably only for inheritance, no constructor-code is required - return; - } - - // create variables and set default values - this.dom = {}; - this.conversion = {}; - this.eventParams = {}; // stores parameters for mouse events - this.groups = []; - this.groupIndexes = {}; - this.items = []; - this.renderQueue = { - show: [], // Items made visible but not yet added to DOM - hide: [], // Items currently visible but not yet removed from DOM - update: [] // Items with changed data but not yet adjusted DOM - }; - this.renderedItems = []; // Items currently rendered in the DOM - this.clusterGenerator = new links.Timeline.ClusterGenerator(this); - this.currentClusters = []; - this.selection = undefined; // stores index and item which is currently selected - - this.listeners = {}; // event listener callbacks - - // Initialize sizes. - // Needed for IE (which gives an error when you try to set an undefined - // value in a style) - this.size = { - 'actualHeight': 0, - 'axis': { - 'characterMajorHeight': 0, - 'characterMajorWidth': 0, - 'characterMinorHeight': 0, - 'characterMinorWidth': 0, - 'height': 0, - 'labelMajorTop': 0, - 'labelMinorTop': 0, - 'line': 0, - 'lineMajorWidth': 0, - 'lineMinorHeight': 0, - 'lineMinorTop': 0, - 'lineMinorWidth': 0, - 'top': 0 - }, - 'contentHeight': 0, - 'contentLeft': 0, - 'contentWidth': 0, - 'frameHeight': 0, - 'frameWidth': 0, - 'groupsLeft': 0, - 'groupsWidth': 0, - 'items': { - 'top': 0 - } - }; - - this.dom.container = container; - - this.options = { - 'width': "100%", - 'height': "auto", - 'minHeight': 0, // minimal height in pixels - 'autoHeight': true, - - 'eventMargin': 10, // minimal margin between events - 'eventMarginAxis': 20, // minimal margin between events and the axis - 'dragAreaWidth': 10, // pixels - - 'min': undefined, - 'max': undefined, - 'zoomMin': 10, // milliseconds - 'zoomMax': 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds - - 'moveable': true, - 'zoomable': true, - 'selectable': true, - 'editable': false, - 'snapEvents': true, - 'groupChangeable': true, - - 'showCurrentTime': true, // show a red bar displaying the current time - 'showCustomTime': false, // show a blue, draggable bar displaying a custom time - 'showMajorLabels': true, - 'showMinorLabels': true, - 'showNavigation': false, - 'showButtonNew': false, - 'groupsOnRight': false, - 'axisOnTop': false, - 'stackEvents': true, - 'animate': true, - 'animateZoom': true, - 'cluster': false, - 'style': 'box', - 'customStackOrder': false, //a function(a,b) for determining stackorder amongst a group of items. Essentially a comparator, -ve value for "a before b" and vice versa - - // i18n: Timeline only has built-in English text per default. Include timeline-locales.js to support more localized text. - 'locale': 'en', - 'MONTHS': new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"), - 'MONTHS_SHORT': new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"), - 'DAYS': new Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"), - 'DAYS_SHORT': new Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"), - 'ZOOM_IN': "Zoom in", - 'ZOOM_OUT': "Zoom out", - 'MOVE_LEFT': "Move left", - 'MOVE_RIGHT': "Move right", - 'NEW': "New", - 'CREATE_NEW_EVENT': "Create new event" - }; - - this.clientTimeOffset = 0; // difference between client time and the time - // set via Timeline.setCurrentTime() - var dom = this.dom; - - // remove all elements from the container element. - while (dom.container.hasChildNodes()) { - dom.container.removeChild(dom.container.firstChild); - } - - // create a step for drawing the axis - this.step = new links.Timeline.StepDate(); - - // add standard item types - this.itemTypes = { - box: links.Timeline.ItemBox, - range: links.Timeline.ItemRange, - dot: links.Timeline.ItemDot - }; - - // initialize data - this.data = []; - this.firstDraw = true; - - // date interval must be initialized - this.setVisibleChartRange(undefined, undefined, false); - - // render for the first time - this.render(); - - // fire the ready event - var me = this; - setTimeout(function () { - me.trigger('ready'); - }, 0); -}; - - -/** - * Main drawing logic. This is the function that needs to be called - * in the html page, to draw the timeline. - * - * A data table with the events must be provided, and an options table. - * - * @param {google.visualization.DataTable} data - * The data containing the events for the timeline. - * Object DataTable is defined in - * google.visualization.DataTable - * @param {Object} options A name/value map containing settings for the - * timeline. Optional. - */ -links.Timeline.prototype.draw = function(data, options) { - this.setOptions(options); - - // read the data - this.setData(data); - - // set timer range. this will also redraw the timeline - if (options && (options.start || options.end)) { - this.setVisibleChartRange(options.start, options.end); - } - else if (this.firstDraw) { - this.setVisibleChartRangeAuto(); - } - - this.firstDraw = false; -}; - - -/** - * Set options for the timeline. - * Timeline must be redrawn afterwards - * @param {Object} options A name/value map containing settings for the - * timeline. Optional. - */ -links.Timeline.prototype.setOptions = function(options) { - if (options) { - // retrieve parameter values - for (var i in options) { - if (options.hasOwnProperty(i)) { - this.options[i] = options[i]; - } - } - - // prepare i18n dependent on set locale - if (typeof links.locales !== 'undefined' && this.options.locale !== 'en') { - var localeOpts = links.locales[this.options.locale]; - if(localeOpts) { - for (var l in localeOpts) { - if (localeOpts.hasOwnProperty(l)) { - this.options[l] = localeOpts[l]; - } - } - } - } - - // check for deprecated options - if (options.showButtonAdd != undefined) { - this.options.showButtonNew = options.showButtonAdd; - console.log('WARNING: Option showButtonAdd is deprecated. Use showButtonNew instead'); - } - if (options.intervalMin != undefined) { - this.options.zoomMin = options.intervalMin; - console.log('WARNING: Option intervalMin is deprecated. Use zoomMin instead'); - } - if (options.intervalMax != undefined) { - this.options.zoomMax = options.intervalMax; - console.log('WARNING: Option intervalMax is deprecated. Use zoomMax instead'); - } - - if (options.scale && options.step) { - this.step.setScale(options.scale, options.step); - } - } - - // validate options - this.options.autoHeight = (this.options.height === "auto"); -}; - -/** - * Add new type of items - * @param {String} typeName Name of new type - * @param {links.Timeline.Item} typeFactory Constructor of items - */ -links.Timeline.prototype.addItemType = function (typeName, typeFactory) { - this.itemTypes[typeName] = typeFactory; -}; - -/** - * Retrieve a map with the column indexes of the columns by column name. - * For example, the method returns the map - * { - * start: 0, - * end: 1, - * content: 2, - * group: undefined, - * className: undefined - * editable: undefined - * } - * @param {google.visualization.DataTable} dataTable - * @type {Object} map - */ -links.Timeline.mapColumnIds = function (dataTable) { - var cols = {}, - colMax = dataTable.getNumberOfColumns(), - allUndefined = true; - - // loop over the columns, and map the column id's to the column indexes - for (var col = 0; col < colMax; col++) { - var id = dataTable.getColumnId(col) || dataTable.getColumnLabel(col); - cols[id] = col; - if (id == 'start' || id == 'end' || id == 'content' || - id == 'group' || id == 'className' || id == 'editable') { - allUndefined = false; - } - } - - // if no labels or ids are defined, - // use the default mapping for start, end, content - if (allUndefined) { - cols.start = 0; - cols.end = 1; - cols.content = 2; - } - - return cols; -}; - -/** - * Set data for the timeline - * @param {google.visualization.DataTable | Array} data - */ -links.Timeline.prototype.setData = function(data) { - // unselect any previously selected item - this.unselectItem(); - - if (!data) { - data = []; - } - - // clear all data - this.stackCancelAnimation(); - this.clearItems(); - this.data = data; - var items = this.items; - this.deleteGroups(); - - if (google && google.visualization && - data instanceof google.visualization.DataTable) { - // map the datatable columns - var cols = links.Timeline.mapColumnIds(data); - - // read DataTable - for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) { - items.push(this.createItem({ - 'start': ((cols.start != undefined) ? data.getValue(row, cols.start) : undefined), - 'end': ((cols.end != undefined) ? data.getValue(row, cols.end) : undefined), - 'content': ((cols.content != undefined) ? data.getValue(row, cols.content) : undefined), - 'group': ((cols.group != undefined) ? data.getValue(row, cols.group) : undefined), - 'className': ((cols.className != undefined) ? data.getValue(row, cols.className) : undefined), - 'editable': ((cols.editable != undefined) ? data.getValue(row, cols.editable) : undefined) - })); - } - } - else if (links.Timeline.isArray(data)) { - // read JSON array - for (var row = 0, rows = data.length; row < rows; row++) { - var itemData = data[row]; - var item = this.createItem(itemData); - items.push(item); - } - } - else { - throw "Unknown data type. DataTable or Array expected."; - } - - // prepare data for clustering, by filtering and sorting by type - if (this.options.cluster) { - this.clusterGenerator.setData(this.items); - } - - this.render({ - animate: false - }); -}; - -/** - * Return the original data table. - * @return {google.visualization.DataTable | Array} data - */ -links.Timeline.prototype.getData = function () { - return this.data; -}; - - -/** - * Update the original data with changed start, end or group. - * - * @param {Number} index - * @param {Object} values An object containing some of the following parameters: - * {Date} start, - * {Date} end, - * {String} content, - * {String} group - */ -links.Timeline.prototype.updateData = function (index, values) { - var data = this.data, - prop; - - if (google && google.visualization && - data instanceof google.visualization.DataTable) { - // update the original google DataTable - var missingRows = (index + 1) - data.getNumberOfRows(); - if (missingRows > 0) { - data.addRows(missingRows); - } - - // map the column id's by name - var cols = links.Timeline.mapColumnIds(data); - - // merge all fields from the provided data into the current data - for (prop in values) { - if (values.hasOwnProperty(prop)) { - var col = cols[prop]; - if (col == undefined) { - // create new column - var value = values[prop]; - var valueType = 'string'; - if (typeof(value) == 'number') {valueType = 'number';} - else if (typeof(value) == 'boolean') {valueType = 'boolean';} - else if (value instanceof Date) {valueType = 'datetime';} - col = data.addColumn(valueType, prop); - } - data.setValue(index, col, values[prop]); - - // TODO: correctly serialize the start and end Date to the desired type (Date, String, or Number) - } - } - } - else if (links.Timeline.isArray(data)) { - // update the original JSON table - var row = data[index]; - if (row == undefined) { - row = {}; - data[index] = row; - } - - // merge all fields from the provided data into the current data - for (prop in values) { - if (values.hasOwnProperty(prop)) { - row[prop] = values[prop]; - - // TODO: correctly serialize the start and end Date to the desired type (Date, String, or Number) - } - } - } - else { - throw "Cannot update data, unknown type of data"; - } -}; - -/** - * Find the item index from a given HTML element - * If no item index is found, undefined is returned - * @param {Element} element - * @return {Number | undefined} index - */ -links.Timeline.prototype.getItemIndex = function(element) { - var e = element, - dom = this.dom, - frame = dom.items.frame, - items = this.items, - index = undefined; - - // try to find the frame where the items are located in - while (e.parentNode && e.parentNode !== frame) { - e = e.parentNode; - } - - if (e.parentNode === frame) { - // yes! we have found the parent element of all items - // retrieve its id from the array with items - for (var i = 0, iMax = items.length; i < iMax; i++) { - if (items[i].dom === e) { - index = i; - break; - } - } - } - - return index; -}; - -/** - * Set a new size for the timeline - * @param {string} width Width in pixels or percentage (for example "800px" - * or "50%") - * @param {string} height Height in pixels or percentage (for example "400px" - * or "30%") - */ -links.Timeline.prototype.setSize = function(width, height) { - if (width) { - this.options.width = width; - this.dom.frame.style.width = width; - } - if (height) { - this.options.height = height; - this.options.autoHeight = (this.options.height === "auto"); - if (height !== "auto" ) { - this.dom.frame.style.height = height; - } - } - - this.render({ - animate: false - }); -}; - - -/** - * Set a new value for the visible range int the timeline. - * Set start undefined to include everything from the earliest date to end. - * Set end undefined to include everything from start to the last date. - * Example usage: - * myTimeline.setVisibleChartRange(new Date("2010-08-22"), - * new Date("2010-09-13")); - * @param {Date} start The start date for the timeline. optional - * @param {Date} end The end date for the timeline. optional - * @param {boolean} redraw Optional. If true (default) the Timeline is - * directly redrawn - */ -links.Timeline.prototype.setVisibleChartRange = function(start, end, redraw) { - var range = {}; - if (!start || !end) { - // retrieve the date range of the items - range = this.getDataRange(true); - } - - if (!start) { - if (end) { - if (range.min && range.min.valueOf() < end.valueOf()) { - // start of the data - start = range.min; - } - else { - // 7 days before the end - start = new Date(end.valueOf()); - start.setDate(start.getDate() - 7); - } - } - else { - // default of 3 days ago - start = new Date(); - start.setDate(start.getDate() - 3); - } - } - - if (!end) { - if (range.max) { - // end of the data - end = range.max; - } - else { - // 7 days after start - end = new Date(start.valueOf()); - end.setDate(end.getDate() + 7); - } - } - - // prevent start Date <= end Date - if (end <= start) { - end = new Date(start.valueOf()); - end.setDate(end.getDate() + 7); - } - - // limit to the allowed range (don't let this do by applyRange, - // because that method will try to maintain the interval (end-start) - var min = this.options.min ? this.options.min : undefined; // date - if (min != undefined && start.valueOf() < min.valueOf()) { - start = new Date(min.valueOf()); // date - } - var max = this.options.max ? this.options.max : undefined; // date - if (max != undefined && end.valueOf() > max.valueOf()) { - end = new Date(max.valueOf()); // date - } - - this.applyRange(start, end); - - if (redraw == undefined || redraw == true) { - this.render({ - animate: false - }); // TODO: optimize, no reflow needed - } - else { - this.recalcConversion(); - } -}; - - -/** - * Change the visible chart range such that all items become visible - */ -links.Timeline.prototype.setVisibleChartRangeAuto = function() { - var range = this.getDataRange(true); - this.setVisibleChartRange(range.min, range.max); -}; - -/** - * Adjust the visible range such that the current time is located in the center - * of the timeline - */ -links.Timeline.prototype.setVisibleChartRangeNow = function() { - var now = new Date(); - - var diff = (this.end.valueOf() - this.start.valueOf()); - - var startNew = new Date(now.valueOf() - diff/2); - var endNew = new Date(startNew.valueOf() + diff); - this.setVisibleChartRange(startNew, endNew); -}; - - -/** - * Retrieve the current visible range in the timeline. - * @return {Object} An object with start and end properties - */ -links.Timeline.prototype.getVisibleChartRange = function() { - return { - 'start': new Date(this.start.valueOf()), - 'end': new Date(this.end.valueOf()) - }; -}; - -/** - * Get the date range of the items. - * @param {boolean} [withMargin] If true, 5% of whitespace is added to the - * left and right of the range. Default is false. - * @return {Object} range An object with parameters min and max. - * - {Date} min is the lowest start date of the items - * - {Date} max is the highest start or end date of the items - * If no data is available, the values of min and max - * will be undefined - */ -links.Timeline.prototype.getDataRange = function (withMargin) { - var items = this.items, - min = undefined, // number - max = undefined; // number - - if (items) { - for (var i = 0, iMax = items.length; i < iMax; i++) { - var item = items[i], - start = item.start != undefined ? item.start.valueOf() : undefined, - end = item.end != undefined ? item.end.valueOf() : start; - - if (min != undefined && start != undefined) { - min = Math.min(min.valueOf(), start.valueOf()); - } - else { - min = start; - } - - if (max != undefined && end != undefined) { - max = Math.max(max, end); - } - else { - max = end; - } - } - } - - if (min && max && withMargin) { - // zoom out 5% such that you have a little white space on the left and right - var diff = (max - min); - min = min - diff * 0.05; - max = max + diff * 0.05; - } - - return { - 'min': min != undefined ? new Date(min) : undefined, - 'max': max != undefined ? new Date(max) : undefined - }; -}; - -/** - * Re-render (reflow and repaint) all components of the Timeline: frame, axis, - * items, ... - * @param {Object} [options] Available options: - * {boolean} renderTimesLeft Number of times the - * render may be repeated - * 5 times by default. - * {boolean} animate takes options.animate - * as default value - */ -links.Timeline.prototype.render = function(options) { - var frameResized = this.reflowFrame(); - var axisResized = this.reflowAxis(); - var groupsResized = this.reflowGroups(); - var itemsResized = this.reflowItems(); - var resized = (frameResized || axisResized || groupsResized || itemsResized); - - // TODO: only stackEvents/filterItems when resized or changed. (gives a bootstrap issue). - // if (resized) { - var animate = this.options.animate; - if (options && options.animate != undefined) { - animate = options.animate; - } - - this.recalcConversion(); - this.clusterItems(); - this.filterItems(); - this.stackItems(animate); - - this.recalcItems(); - - // TODO: only repaint when resized or when filterItems or stackItems gave a change? - var needsReflow = this.repaint(); - - // re-render once when needed (prevent endless re-render loop) - if (needsReflow) { - var renderTimesLeft = options ? options.renderTimesLeft : undefined; - if (renderTimesLeft == undefined) { - renderTimesLeft = 5; - } - if (renderTimesLeft > 0) { - this.render({ - 'animate': options ? options.animate: undefined, - 'renderTimesLeft': (renderTimesLeft - 1) - }); - } - } -}; - -/** - * Repaint all components of the Timeline - * @return {boolean} needsReflow Returns true if the DOM is changed such that - * a reflow is needed. - */ -links.Timeline.prototype.repaint = function() { - var frameNeedsReflow = this.repaintFrame(); - var axisNeedsReflow = this.repaintAxis(); - var groupsNeedsReflow = this.repaintGroups(); - var itemsNeedsReflow = this.repaintItems(); - this.repaintCurrentTime(); - this.repaintCustomTime(); - - return (frameNeedsReflow || axisNeedsReflow || groupsNeedsReflow || itemsNeedsReflow); -}; - -/** - * Reflow the timeline frame - * @return {boolean} resized Returns true if any of the frame elements - * have been resized. - */ -links.Timeline.prototype.reflowFrame = function() { - var dom = this.dom, - options = this.options, - size = this.size, - resized = false; - - // Note: IE7 has issues with giving frame.clientWidth, therefore I use offsetWidth instead - var frameWidth = dom.frame ? dom.frame.offsetWidth : 0, - frameHeight = dom.frame ? dom.frame.clientHeight : 0; - - resized = resized || (size.frameWidth !== frameWidth); - resized = resized || (size.frameHeight !== frameHeight); - size.frameWidth = frameWidth; - size.frameHeight = frameHeight; - - return resized; -}; - -/** - * repaint the Timeline frame - * @return {boolean} needsReflow Returns true if the DOM is changed such that - * a reflow is needed. - */ -links.Timeline.prototype.repaintFrame = function() { - var needsReflow = false, - dom = this.dom, - options = this.options, - size = this.size; - - // main frame - if (!dom.frame) { - dom.frame = document.createElement("DIV"); - dom.frame.className = "timeline-frame"; - dom.frame.style.position = "relative"; - dom.frame.style.overflow = "hidden"; - dom.container.appendChild(dom.frame); - needsReflow = true; - } - - var height = options.autoHeight ? - (size.actualHeight + "px") : - (options.height || "100%"); - var width = options.width || "100%"; - needsReflow = needsReflow || (dom.frame.style.height != height); - needsReflow = needsReflow || (dom.frame.style.width != width); - dom.frame.style.height = height; - dom.frame.style.width = width; - - // contents - if (!dom.content) { - // create content box where the axis and items will be created - dom.content = document.createElement("DIV"); - dom.content.style.position = "relative"; - dom.content.style.overflow = "hidden"; - dom.frame.appendChild(dom.content); - - var timelines = document.createElement("DIV"); - timelines.style.position = "absolute"; - timelines.style.left = "0px"; - timelines.style.top = "0px"; - timelines.style.height = "100%"; - timelines.style.width = "0px"; - dom.content.appendChild(timelines); - dom.contentTimelines = timelines; - - var params = this.eventParams, - me = this; - if (!params.onMouseDown) { - params.onMouseDown = function (event) {me.onMouseDown(event);}; - links.Timeline.addEventListener(dom.content, "mousedown", params.onMouseDown); - } - if (!params.onTouchStart) { - params.onTouchStart = function (event) {me.onTouchStart(event);}; - links.Timeline.addEventListener(dom.content, "touchstart", params.onTouchStart); - } - if (!params.onMouseWheel) { - params.onMouseWheel = function (event) {me.onMouseWheel(event);}; - links.Timeline.addEventListener(dom.content, "mousewheel", params.onMouseWheel); - } - if (!params.onDblClick) { - params.onDblClick = function (event) {me.onDblClick(event);}; - links.Timeline.addEventListener(dom.content, "dblclick", params.onDblClick); - } - - needsReflow = true; - } - dom.content.style.left = size.contentLeft + "px"; - dom.content.style.top = "0px"; - dom.content.style.width = size.contentWidth + "px"; - dom.content.style.height = size.frameHeight + "px"; - - this.repaintNavigation(); - - return needsReflow; -}; - -/** - * Reflow the timeline axis. Calculate its height, width, positioning, etc... - * @return {boolean} resized returns true if the axis is resized - */ -links.Timeline.prototype.reflowAxis = function() { - var resized = false, - dom = this.dom, - options = this.options, - size = this.size, - axisDom = dom.axis; - - var characterMinorWidth = (axisDom && axisDom.characterMinor) ? axisDom.characterMinor.clientWidth : 0, - characterMinorHeight = (axisDom && axisDom.characterMinor) ? axisDom.characterMinor.clientHeight : 0, - characterMajorWidth = (axisDom && axisDom.characterMajor) ? axisDom.characterMajor.clientWidth : 0, - characterMajorHeight = (axisDom && axisDom.characterMajor) ? axisDom.characterMajor.clientHeight : 0, - axisHeight = (options.showMinorLabels ? characterMinorHeight : 0) + - (options.showMajorLabels ? characterMajorHeight : 0); - - var axisTop = options.axisOnTop ? 0 : size.frameHeight - axisHeight, - axisLine = options.axisOnTop ? axisHeight : axisTop; - - resized = resized || (size.axis.top !== axisTop); - resized = resized || (size.axis.line !== axisLine); - resized = resized || (size.axis.height !== axisHeight); - size.axis.top = axisTop; - size.axis.line = axisLine; - size.axis.height = axisHeight; - size.axis.labelMajorTop = options.axisOnTop ? 0 : axisLine + - (options.showMinorLabels ? characterMinorHeight : 0); - size.axis.labelMinorTop = options.axisOnTop ? - (options.showMajorLabels ? characterMajorHeight : 0) : - axisLine; - size.axis.lineMinorTop = options.axisOnTop ? size.axis.labelMinorTop : 0; - size.axis.lineMinorHeight = options.showMajorLabels ? - size.frameHeight - characterMajorHeight: - size.frameHeight; - if (axisDom && axisDom.minorLines && axisDom.minorLines.length) { - size.axis.lineMinorWidth = axisDom.minorLines[0].offsetWidth; - } - else { - size.axis.lineMinorWidth = 1; - } - if (axisDom && axisDom.majorLines && axisDom.majorLines.length) { - size.axis.lineMajorWidth = axisDom.majorLines[0].offsetWidth; - } - else { - size.axis.lineMajorWidth = 1; - } - - resized = resized || (size.axis.characterMinorWidth !== characterMinorWidth); - resized = resized || (size.axis.characterMinorHeight !== characterMinorHeight); - resized = resized || (size.axis.characterMajorWidth !== characterMajorWidth); - resized = resized || (size.axis.characterMajorHeight !== characterMajorHeight); - size.axis.characterMinorWidth = characterMinorWidth; - size.axis.characterMinorHeight = characterMinorHeight; - size.axis.characterMajorWidth = characterMajorWidth; - size.axis.characterMajorHeight = characterMajorHeight; - - var contentHeight = Math.max(size.frameHeight - axisHeight, 0); - size.contentLeft = options.groupsOnRight ? 0 : size.groupsWidth; - size.contentWidth = Math.max(size.frameWidth - size.groupsWidth, 0); - size.contentHeight = contentHeight; - - return resized; -}; - -/** - * Redraw the timeline axis with minor and major labels - * @return {boolean} needsReflow Returns true if the DOM is changed such - * that a reflow is needed. - */ -links.Timeline.prototype.repaintAxis = function() { - var needsReflow = false, - dom = this.dom, - options = this.options, - size = this.size, - step = this.step; - - var axis = dom.axis; - if (!axis) { - axis = {}; - dom.axis = axis; - } - if (!size.axis.properties) { - size.axis.properties = {}; - } - if (!axis.minorTexts) { - axis.minorTexts = []; - } - if (!axis.minorLines) { - axis.minorLines = []; - } - if (!axis.majorTexts) { - axis.majorTexts = []; - } - if (!axis.majorLines) { - axis.majorLines = []; - } - - if (!axis.frame) { - axis.frame = document.createElement("DIV"); - axis.frame.style.position = "absolute"; - axis.frame.style.left = "0px"; - axis.frame.style.top = "0px"; - dom.content.appendChild(axis.frame); - } - - // take axis offline - dom.content.removeChild(axis.frame); - - axis.frame.style.width = (size.contentWidth) + "px"; - axis.frame.style.height = (size.axis.height) + "px"; - - // the drawn axis is more wide than the actual visual part, such that - // the axis can be dragged without having to redraw it each time again. - var start = this.screenToTime(0); - var end = this.screenToTime(size.contentWidth); - - // calculate minimum step (in milliseconds) based on character size - if (size.axis.characterMinorWidth) { - this.minimumStep = this.screenToTime(size.axis.characterMinorWidth * 6) - - this.screenToTime(0); - - step.setRange(start, end, this.minimumStep); - } - - var charsNeedsReflow = this.repaintAxisCharacters(); - needsReflow = needsReflow || charsNeedsReflow; - - // The current labels on the axis will be re-used (much better performance), - // therefore, the repaintAxis method uses the mechanism with - // repaintAxisStartOverwriting, repaintAxisEndOverwriting, and - // this.size.axis.properties is used. - this.repaintAxisStartOverwriting(); - - step.start(); - var xFirstMajorLabel = undefined; - var max = 0; - while (!step.end() && max < 1000) { - max++; - var cur = step.getCurrent(), - x = this.timeToScreen(cur), - isMajor = step.isMajor(); - - if (options.showMinorLabels) { - this.repaintAxisMinorText(x, step.getLabelMinor(options)); - } - - if (isMajor && options.showMajorLabels) { - if (x > 0) { - if (xFirstMajorLabel == undefined) { - xFirstMajorLabel = x; - } - this.repaintAxisMajorText(x, step.getLabelMajor(options)); - } - this.repaintAxisMajorLine(x); - } - else { - this.repaintAxisMinorLine(x); - } - - step.next(); - } - - // create a major label on the left when needed - if (options.showMajorLabels) { - var leftTime = this.screenToTime(0), - leftText = this.step.getLabelMajor(options, leftTime), - width = leftText.length * size.axis.characterMajorWidth + 10; // upper bound estimation - - if (xFirstMajorLabel == undefined || width < xFirstMajorLabel) { - this.repaintAxisMajorText(0, leftText, leftTime); - } - } - - // cleanup left over labels - this.repaintAxisEndOverwriting(); - - this.repaintAxisHorizontal(); - - // put axis online - dom.content.insertBefore(axis.frame, dom.content.firstChild); - - return needsReflow; -}; - -/** - * Create characters used to determine the size of text on the axis - * @return {boolean} needsReflow Returns true if the DOM is changed such that - * a reflow is needed. - */ -links.Timeline.prototype.repaintAxisCharacters = function () { - // calculate the width and height of a single character - // this is used to calculate the step size, and also the positioning of the - // axis - var needsReflow = false, - dom = this.dom, - axis = dom.axis, - text; - - if (!axis.characterMinor) { - text = document.createTextNode("0"); - var characterMinor = document.createElement("DIV"); - characterMinor.className = "timeline-axis-text timeline-axis-text-minor"; - characterMinor.appendChild(text); - characterMinor.style.position = "absolute"; - characterMinor.style.visibility = "hidden"; - characterMinor.style.paddingLeft = "0px"; - characterMinor.style.paddingRight = "0px"; - axis.frame.appendChild(characterMinor); - - axis.characterMinor = characterMinor; - needsReflow = true; - } - - if (!axis.characterMajor) { - text = document.createTextNode("0"); - var characterMajor = document.createElement("DIV"); - characterMajor.className = "timeline-axis-text timeline-axis-text-major"; - characterMajor.appendChild(text); - characterMajor.style.position = "absolute"; - characterMajor.style.visibility = "hidden"; - characterMajor.style.paddingLeft = "0px"; - characterMajor.style.paddingRight = "0px"; - axis.frame.appendChild(characterMajor); - - axis.characterMajor = characterMajor; - needsReflow = true; - } - - return needsReflow; -}; - -/** - * Initialize redraw of the axis. All existing labels and lines will be - * overwritten and reused. - */ -links.Timeline.prototype.repaintAxisStartOverwriting = function () { - var properties = this.size.axis.properties; - - properties.minorTextNum = 0; - properties.minorLineNum = 0; - properties.majorTextNum = 0; - properties.majorLineNum = 0; -}; - -/** - * End of overwriting HTML DOM elements of the axis. - * remaining elements will be removed - */ -links.Timeline.prototype.repaintAxisEndOverwriting = function () { - var dom = this.dom, - props = this.size.axis.properties, - frame = this.dom.axis.frame, - num; - - // remove leftovers - var minorTexts = dom.axis.minorTexts; - num = props.minorTextNum; - while (minorTexts.length > num) { - var minorText = minorTexts[num]; - frame.removeChild(minorText); - minorTexts.splice(num, 1); - } - - var minorLines = dom.axis.minorLines; - num = props.minorLineNum; - while (minorLines.length > num) { - var minorLine = minorLines[num]; - frame.removeChild(minorLine); - minorLines.splice(num, 1); - } - - var majorTexts = dom.axis.majorTexts; - num = props.majorTextNum; - while (majorTexts.length > num) { - var majorText = majorTexts[num]; - frame.removeChild(majorText); - majorTexts.splice(num, 1); - } - - var majorLines = dom.axis.majorLines; - num = props.majorLineNum; - while (majorLines.length > num) { - var majorLine = majorLines[num]; - frame.removeChild(majorLine); - majorLines.splice(num, 1); - } -}; - -/** - * Repaint the horizontal line and background of the axis - */ -links.Timeline.prototype.repaintAxisHorizontal = function() { - var axis = this.dom.axis, - size = this.size, - options = this.options; - - // line behind all axis elements (possibly having a background color) - var hasAxis = (options.showMinorLabels || options.showMajorLabels); - if (hasAxis) { - if (!axis.backgroundLine) { - // create the axis line background (for a background color or so) - var backgroundLine = document.createElement("DIV"); - backgroundLine.className = "timeline-axis"; - backgroundLine.style.position = "absolute"; - backgroundLine.style.left = "0px"; - backgroundLine.style.width = "100%"; - backgroundLine.style.border = "none"; - axis.frame.insertBefore(backgroundLine, axis.frame.firstChild); - - axis.backgroundLine = backgroundLine; - } - - if (axis.backgroundLine) { - axis.backgroundLine.style.top = size.axis.top + "px"; - axis.backgroundLine.style.height = size.axis.height + "px"; - } - } - else { - if (axis.backgroundLine) { - axis.frame.removeChild(axis.backgroundLine); - delete axis.backgroundLine; - } - } - - // line before all axis elements - if (hasAxis) { - if (axis.line) { - // put this line at the end of all childs - var line = axis.frame.removeChild(axis.line); - axis.frame.appendChild(line); - } - else { - // make the axis line - var line = document.createElement("DIV"); - line.className = "timeline-axis"; - line.style.position = "absolute"; - line.style.left = "0px"; - line.style.width = "100%"; - line.style.height = "0px"; - axis.frame.appendChild(line); - - axis.line = line; - } - - axis.line.style.top = size.axis.line + "px"; - } - else { - if (axis.line && axis.line.parentElement) { - axis.frame.removeChild(axis.line); - delete axis.line; - } - } -}; - -/** - * Create a minor label for the axis at position x - * @param {Number} x - * @param {String} text - */ -links.Timeline.prototype.repaintAxisMinorText = function (x, text) { - var size = this.size, - dom = this.dom, - props = size.axis.properties, - frame = dom.axis.frame, - minorTexts = dom.axis.minorTexts, - index = props.minorTextNum, - label; - - if (index < minorTexts.length) { - label = minorTexts[index] - } - else { - // create new label - var content = document.createTextNode(""); - label = document.createElement("DIV"); - label.appendChild(content); - label.className = "timeline-axis-text timeline-axis-text-minor"; - label.style.position = "absolute"; - - frame.appendChild(label); - - minorTexts.push(label); - } - - label.childNodes[0].nodeValue = text; - label.style.left = x + "px"; - label.style.top = size.axis.labelMinorTop + "px"; - //label.title = title; // TODO: this is a heavy operation - - props.minorTextNum++; -}; - -/** - * Create a minor line for the axis at position x - * @param {Number} x - */ -links.Timeline.prototype.repaintAxisMinorLine = function (x) { - var axis = this.size.axis, - dom = this.dom, - props = axis.properties, - frame = dom.axis.frame, - minorLines = dom.axis.minorLines, - index = props.minorLineNum, - line; - - if (index < minorLines.length) { - line = minorLines[index]; - } - else { - // create vertical line - line = document.createElement("DIV"); - line.className = "timeline-axis-grid timeline-axis-grid-minor"; - line.style.position = "absolute"; - line.style.width = "0px"; - - frame.appendChild(line); - minorLines.push(line); - } - - line.style.top = axis.lineMinorTop + "px"; - line.style.height = axis.lineMinorHeight + "px"; - line.style.left = (x - axis.lineMinorWidth/2) + "px"; - - props.minorLineNum++; -}; - -/** - * Create a Major label for the axis at position x - * @param {Number} x - * @param {String} text - */ -links.Timeline.prototype.repaintAxisMajorText = function (x, text) { - var size = this.size, - props = size.axis.properties, - frame = this.dom.axis.frame, - majorTexts = this.dom.axis.majorTexts, - index = props.majorTextNum, - label; - - if (index < majorTexts.length) { - label = majorTexts[index]; - } - else { - // create label - var content = document.createTextNode(text); - label = document.createElement("DIV"); - label.className = "timeline-axis-text timeline-axis-text-major"; - label.appendChild(content); - label.style.position = "absolute"; - label.style.top = "0px"; - - frame.appendChild(label); - majorTexts.push(label); - } - - label.childNodes[0].nodeValue = text; - label.style.top = size.axis.labelMajorTop + "px"; - label.style.left = x + "px"; - //label.title = title; // TODO: this is a heavy operation - - props.majorTextNum ++; -}; - -/** - * Create a Major line for the axis at position x - * @param {Number} x - */ -links.Timeline.prototype.repaintAxisMajorLine = function (x) { - var size = this.size, - props = size.axis.properties, - axis = this.size.axis, - frame = this.dom.axis.frame, - majorLines = this.dom.axis.majorLines, - index = props.majorLineNum, - line; - - if (index < majorLines.length) { - line = majorLines[index]; - } - else { - // create vertical line - line = document.createElement("DIV"); - line.className = "timeline-axis-grid timeline-axis-grid-major"; - line.style.position = "absolute"; - line.style.top = "0px"; - line.style.width = "0px"; - - frame.appendChild(line); - majorLines.push(line); - } - - line.style.left = (x - axis.lineMajorWidth/2) + "px"; - line.style.height = size.frameHeight + "px"; - - props.majorLineNum ++; -}; - -/** - * Reflow all items, retrieve their actual size - * @return {boolean} resized returns true if any of the items is resized - */ -links.Timeline.prototype.reflowItems = function() { - var resized = false, - i, - iMax, - group, - groups = this.groups, - renderedItems = this.renderedItems; - - if (groups) { // TODO: need to check if labels exists? - // loop through all groups to reset the items height - groups.forEach(function (group) { - group.itemsHeight = 0; - }); - } - - // loop through the width and height of all visible items - for (i = 0, iMax = renderedItems.length; i < iMax; i++) { - var item = renderedItems[i], - domItem = item.dom; - group = item.group; - - if (domItem) { - // TODO: move updating width and height into item.reflow - var width = domItem ? domItem.clientWidth : 0; - var height = domItem ? domItem.clientHeight : 0; - resized = resized || (item.width != width); - resized = resized || (item.height != height); - item.width = width; - item.height = height; - //item.borderWidth = (domItem.offsetWidth - domItem.clientWidth - 2) / 2; // TODO: borderWidth - item.reflow(); - } - - if (group) { - group.itemsHeight = group.itemsHeight ? - Math.max(group.itemsHeight, item.height) : - item.height; - } - } - - return resized; -}; - -/** - * Recalculate item properties: - * - the height of each group. - * - the actualHeight, from the stacked items or the sum of the group heights - * @return {boolean} resized returns true if any of the items properties is - * changed - */ -links.Timeline.prototype.recalcItems = function () { - var resized = false, - i, - iMax, - item, - finalItem, - finalItems, - group, - groups = this.groups, - size = this.size, - options = this.options, - renderedItems = this.renderedItems; - - var actualHeight = 0; - if (groups.length == 0) { - // calculate actual height of the timeline when there are no groups - // but stacked items - if (options.autoHeight || options.cluster) { - var min = 0, - max = 0; - - if (this.stack && this.stack.finalItems) { - // adjust the offset of all finalItems when the actualHeight has been changed - finalItems = this.stack.finalItems; - finalItem = finalItems[0]; - if (finalItem && finalItem.top) { - min = finalItem.top; - max = finalItem.top + finalItem.height; - } - for (i = 1, iMax = finalItems.length; i < iMax; i++) { - finalItem = finalItems[i]; - min = Math.min(min, finalItem.top); - max = Math.max(max, finalItem.top + finalItem.height); - } - } - else { - item = renderedItems[0]; - if (item && item.top) { - min = item.top; - max = item.top + item.height; - } - for (i = 1, iMax = renderedItems.length; i < iMax; i++) { - item = renderedItems[i]; - if (item.top) { - min = Math.min(min, item.top); - max = Math.max(max, (item.top + item.height)); - } - } - } - - actualHeight = (max - min) + 2 * options.eventMarginAxis + size.axis.height; - if (actualHeight < options.minHeight) { - actualHeight = options.minHeight; - } - - if (size.actualHeight != actualHeight && options.autoHeight && !options.axisOnTop) { - // adjust the offset of all items when the actualHeight has been changed - var diff = actualHeight - size.actualHeight; - if (this.stack && this.stack.finalItems) { - finalItems = this.stack.finalItems; - for (i = 0, iMax = finalItems.length; i < iMax; i++) { - finalItems[i].top += diff; - finalItems[i].item.top += diff; - } - } - else { - for (i = 0, iMax = renderedItems.length; i < iMax; i++) { - renderedItems[i].top += diff; - } - } - } - } - } - else { - // loop through all groups to get the height of each group, and the - // total height - actualHeight = size.axis.height + 2 * options.eventMarginAxis; - for (i = 0, iMax = groups.length; i < iMax; i++) { - group = groups[i]; - - var groupHeight = Math.max(group.labelHeight || 0, group.itemsHeight || 0); - resized = resized || (groupHeight != group.height); - group.height = groupHeight; - - actualHeight += groups[i].height + options.eventMargin; - } - - // calculate top positions of the group labels and lines - var eventMargin = options.eventMargin, - top = options.axisOnTop ? - options.eventMarginAxis + eventMargin/2 : - size.contentHeight - options.eventMarginAxis + eventMargin/ 2, - axisHeight = size.axis.height; - - for (i = 0, iMax = groups.length; i < iMax; i++) { - group = groups[i]; - if (options.axisOnTop) { - group.top = top + axisHeight; - group.labelTop = top + axisHeight + (group.height - group.labelHeight) / 2; - group.lineTop = top + axisHeight + group.height + eventMargin/2; - top += group.height + eventMargin; - } - else { - top -= group.height + eventMargin; - group.top = top; - group.labelTop = top + (group.height - group.labelHeight) / 2; - group.lineTop = top - eventMargin/2; - } - } - - // calculate top position of the visible items - for (i = 0, iMax = renderedItems.length; i < iMax; i++) { - item = renderedItems[i]; - group = item.group; - - if (group) { - item.top = group.top; - } - } - - resized = true; - } - - if (actualHeight < options.minHeight) { - actualHeight = options.minHeight; - } - resized = resized || (actualHeight != size.actualHeight); - size.actualHeight = actualHeight; - - return resized; -}; - -/** - * This method clears the (internal) array this.items in a safe way: neatly - * cleaning up the DOM, and accompanying arrays this.renderedItems and - * the created clusters. - */ -links.Timeline.prototype.clearItems = function() { - // add all visible items to the list to be hidden - var hideItems = this.renderQueue.hide; - this.renderedItems.forEach(function (item) { - hideItems.push(item); - }); - - // clear the cluster generator - this.clusterGenerator.clear(); - - // actually clear the items - this.items = []; -}; - -/** - * Repaint all items - * @return {boolean} needsReflow Returns true if the DOM is changed such that - * a reflow is needed. - */ -links.Timeline.prototype.repaintItems = function() { - var i, iMax, item, index; - - var needsReflow = false, - dom = this.dom, - size = this.size, - timeline = this, - renderedItems = this.renderedItems; - - if (!dom.items) { - dom.items = {}; - } - - // draw the frame containing the items - var frame = dom.items.frame; - if (!frame) { - frame = document.createElement("DIV"); - frame.style.position = "relative"; - dom.content.appendChild(frame); - dom.items.frame = frame; - } - - frame.style.left = "0px"; - frame.style.top = size.items.top + "px"; - frame.style.height = "0px"; - - // Take frame offline (for faster manipulation of the DOM) - dom.content.removeChild(frame); - - // process the render queue with changes - var queue = this.renderQueue; - var newImageUrls = []; - needsReflow = needsReflow || - (queue.show.length > 0) || - (queue.update.length > 0) || - (queue.hide.length > 0); // TODO: reflow needed on hide of items? - - while (item = queue.show.shift()) { - item.showDOM(frame); - item.getImageUrls(newImageUrls); - renderedItems.push(item); - } - while (item = queue.update.shift()) { - item.updateDOM(frame); - item.getImageUrls(newImageUrls); - index = this.renderedItems.indexOf(item); - if (index == -1) { - renderedItems.push(item); - } - } - while (item = queue.hide.shift()) { - item.hideDOM(frame); - index = this.renderedItems.indexOf(item); - if (index != -1) { - renderedItems.splice(index, 1); - } - } - - // reposition all visible items - renderedItems.forEach(function (item) { - item.updatePosition(timeline); - }); - - // redraw the delete button and dragareas of the selected item (if any) - this.repaintDeleteButton(); - this.repaintDragAreas(); - - // put frame online again - dom.content.appendChild(frame); - - if (newImageUrls.length) { - // retrieve all image sources from the items, and set a callback once - // all images are retrieved - var callback = function () { - timeline.render(); - }; - var sendCallbackWhenAlreadyLoaded = false; - links.imageloader.loadAll(newImageUrls, callback, sendCallbackWhenAlreadyLoaded); - } - - return needsReflow; -}; - -/** - * Reflow the size of the groups - * @return {boolean} resized Returns true if any of the frame elements - * have been resized. - */ -links.Timeline.prototype.reflowGroups = function() { - var resized = false, - options = this.options, - size = this.size, - dom = this.dom; - - // calculate the groups width and height - // TODO: only update when data is changed! -> use an updateSeq - var groupsWidth = 0; - - // loop through all groups to get the labels width and height - var groups = this.groups; - var labels = this.dom.groups ? this.dom.groups.labels : []; - for (var i = 0, iMax = groups.length; i < iMax; i++) { - var group = groups[i]; - var label = labels[i]; - group.labelWidth = label ? label.clientWidth : 0; - group.labelHeight = label ? label.clientHeight : 0; - group.width = group.labelWidth; // TODO: group.width is redundant with labelWidth - - groupsWidth = Math.max(groupsWidth, group.width); - } - - // limit groupsWidth to the groups width in the options - if (options.groupsWidth !== undefined) { - groupsWidth = dom.groups.frame ? dom.groups.frame.clientWidth : 0; - } - - // compensate for the border width. TODO: calculate the real border width - groupsWidth += 1; - - var groupsLeft = options.groupsOnRight ? size.frameWidth - groupsWidth : 0; - resized = resized || (size.groupsWidth !== groupsWidth); - resized = resized || (size.groupsLeft !== groupsLeft); - size.groupsWidth = groupsWidth; - size.groupsLeft = groupsLeft; - - return resized; -}; - -/** - * Redraw the group labels - */ -links.Timeline.prototype.repaintGroups = function() { - var dom = this.dom, - timeline = this, - options = this.options, - size = this.size, - groups = this.groups; - - if (dom.groups === undefined) { - dom.groups = {}; - } - - var labels = dom.groups.labels; - if (!labels) { - labels = []; - dom.groups.labels = labels; - } - var labelLines = dom.groups.labelLines; - if (!labelLines) { - labelLines = []; - dom.groups.labelLines = labelLines; - } - var itemLines = dom.groups.itemLines; - if (!itemLines) { - itemLines = []; - dom.groups.itemLines = itemLines; - } - - // create the frame for holding the groups - var frame = dom.groups.frame; - if (!frame) { - frame = document.createElement("DIV"); - frame.className = "timeline-groups-axis"; - frame.style.position = "absolute"; - frame.style.overflow = "hidden"; - frame.style.top = "0px"; - frame.style.height = "100%"; - - dom.frame.appendChild(frame); - dom.groups.frame = frame; - } - - frame.style.left = size.groupsLeft + "px"; - frame.style.width = (options.groupsWidth !== undefined) ? - options.groupsWidth : - size.groupsWidth + "px"; - - // hide groups axis when there are no groups - if (groups.length == 0) { - frame.style.display = 'none'; - } - else { - frame.style.display = ''; - } - - // TODO: only create/update groups when data is changed. - - // create the items - var current = labels.length, - needed = groups.length; - - // overwrite existing group labels - for (var i = 0, iMax = Math.min(current, needed); i < iMax; i++) { - var group = groups[i]; - var label = labels[i]; - label.innerHTML = this.getGroupName(group); - label.style.display = ''; - } - - // append new items when needed - for (var i = current; i < needed; i++) { - var group = groups[i]; - - // create text label - var label = document.createElement("DIV"); - label.className = "timeline-groups-text"; - label.style.position = "absolute"; - if (options.groupsWidth === undefined) { - label.style.whiteSpace = "nowrap"; - } - label.innerHTML = this.getGroupName(group); - frame.appendChild(label); - labels[i] = label; - - // create the grid line between the group labels - var labelLine = document.createElement("DIV"); - labelLine.className = "timeline-axis-grid timeline-axis-grid-minor"; - labelLine.style.position = "absolute"; - labelLine.style.left = "0px"; - labelLine.style.width = "100%"; - labelLine.style.height = "0px"; - labelLine.style.borderTopStyle = "solid"; - frame.appendChild(labelLine); - labelLines[i] = labelLine; - - // create the grid line between the items - var itemLine = document.createElement("DIV"); - itemLine.className = "timeline-axis-grid timeline-axis-grid-minor"; - itemLine.style.position = "absolute"; - itemLine.style.left = "0px"; - itemLine.style.width = "100%"; - itemLine.style.height = "0px"; - itemLine.style.borderTopStyle = "solid"; - dom.content.insertBefore(itemLine, dom.content.firstChild); - itemLines[i] = itemLine; - } - - // remove redundant items from the DOM when needed - for (var i = needed; i < current; i++) { - var label = labels[i], - labelLine = labelLines[i], - itemLine = itemLines[i]; - - frame.removeChild(label); - frame.removeChild(labelLine); - dom.content.removeChild(itemLine); - } - labels.splice(needed, current - needed); - labelLines.splice(needed, current - needed); - itemLines.splice(needed, current - needed); - - frame.style.borderStyle = options.groupsOnRight ? - "none none none solid" : - "none solid none none"; - - // position the groups - for (var i = 0, iMax = groups.length; i < iMax; i++) { - var group = groups[i], - label = labels[i], - labelLine = labelLines[i], - itemLine = itemLines[i]; - - label.style.top = group.labelTop + "px"; - labelLine.style.top = group.lineTop + "px"; - itemLine.style.top = group.lineTop + "px"; - itemLine.style.width = size.contentWidth + "px"; - } - - if (!dom.groups.background) { - // create the axis grid line background - var background = document.createElement("DIV"); - background.className = "timeline-axis"; - background.style.position = "absolute"; - background.style.left = "0px"; - background.style.width = "100%"; - background.style.border = "none"; - - frame.appendChild(background); - dom.groups.background = background; - } - dom.groups.background.style.top = size.axis.top + 'px'; - dom.groups.background.style.height = size.axis.height + 'px'; - - if (!dom.groups.line) { - // create the axis grid line - var line = document.createElement("DIV"); - line.className = "timeline-axis"; - line.style.position = "absolute"; - line.style.left = "0px"; - line.style.width = "100%"; - line.style.height = "0px"; - - frame.appendChild(line); - dom.groups.line = line; - } - dom.groups.line.style.top = size.axis.line + 'px'; - - // create a callback when there are images which are not yet loaded - // TODO: more efficiently load images in the groups - if (dom.groups.frame && groups.length) { - var imageUrls = []; - links.imageloader.filterImageUrls(dom.groups.frame, imageUrls); - if (imageUrls.length) { - // retrieve all image sources from the items, and set a callback once - // all images are retrieved - var callback = function () { - timeline.render(); - }; - var sendCallbackWhenAlreadyLoaded = false; - links.imageloader.loadAll(imageUrls, callback, sendCallbackWhenAlreadyLoaded); - } - } -}; - - -/** - * Redraw the current time bar - */ -links.Timeline.prototype.repaintCurrentTime = function() { - var options = this.options, - dom = this.dom, - size = this.size; - - if (!options.showCurrentTime) { - if (dom.currentTime) { - dom.contentTimelines.removeChild(dom.currentTime); - delete dom.currentTime; - } - - return; - } - - if (!dom.currentTime) { - // create the current time bar - var currentTime = document.createElement("DIV"); - currentTime.className = "timeline-currenttime"; - currentTime.style.position = "absolute"; - currentTime.style.top = "0px"; - currentTime.style.height = "100%"; - - dom.contentTimelines.appendChild(currentTime); - dom.currentTime = currentTime; - } - - var now = new Date(); - var nowOffset = new Date(now.valueOf() + this.clientTimeOffset); - var x = this.timeToScreen(nowOffset); - - var visible = (x > -size.contentWidth && x < 2 * size.contentWidth); - dom.currentTime.style.display = visible ? '' : 'none'; - dom.currentTime.style.left = x + "px"; - dom.currentTime.title = "Current time: " + nowOffset; - - // start a timer to adjust for the new time - if (this.currentTimeTimer != undefined) { - clearTimeout(this.currentTimeTimer); - delete this.currentTimeTimer; - } - var timeline = this; - var onTimeout = function() { - timeline.repaintCurrentTime(); - }; - // the time equal to the width of one pixel, divided by 2 for more smoothness - var interval = 1 / this.conversion.factor / 2; - if (interval < 30) interval = 30; - this.currentTimeTimer = setTimeout(onTimeout, interval); -}; - -/** - * Redraw the custom time bar - */ -links.Timeline.prototype.repaintCustomTime = function() { - var options = this.options, - dom = this.dom, - size = this.size; - - if (!options.showCustomTime) { - if (dom.customTime) { - dom.contentTimelines.removeChild(dom.customTime); - delete dom.customTime; - } - - return; - } - - if (!dom.customTime) { - var customTime = document.createElement("DIV"); - customTime.className = "timeline-customtime"; - customTime.style.position = "absolute"; - customTime.style.top = "0px"; - customTime.style.height = "100%"; - - var drag = document.createElement("DIV"); - drag.style.position = "relative"; - drag.style.top = "0px"; - drag.style.left = "-10px"; - drag.style.height = "100%"; - drag.style.width = "20px"; - customTime.appendChild(drag); - - dom.contentTimelines.appendChild(customTime); - dom.customTime = customTime; - - // initialize parameter - this.customTime = new Date(); - } - - var x = this.timeToScreen(this.customTime), - visible = (x > -size.contentWidth && x < 2 * size.contentWidth); - dom.customTime.style.display = visible ? '' : 'none'; - dom.customTime.style.left = x + "px"; - dom.customTime.title = "Time: " + this.customTime; -}; - - -/** - * Redraw the delete button, on the top right of the currently selected item - * if there is no item selected, the button is hidden. - */ -links.Timeline.prototype.repaintDeleteButton = function () { - var timeline = this, - dom = this.dom, - frame = dom.items.frame; - - var deleteButton = dom.items.deleteButton; - if (!deleteButton) { - // create a delete button - deleteButton = document.createElement("DIV"); - deleteButton.className = "timeline-navigation-delete"; - deleteButton.style.position = "absolute"; - - frame.appendChild(deleteButton); - dom.items.deleteButton = deleteButton; - } - - var index = this.selection ? this.selection.index : -1, - item = this.selection ? this.items[index] : undefined; - if (item && item.rendered && this.isEditable(item)) { - var right = item.getRight(this), - top = item.top; - - deleteButton.style.left = right + 'px'; - deleteButton.style.top = top + 'px'; - deleteButton.style.display = ''; - frame.removeChild(deleteButton); - frame.appendChild(deleteButton); - } - else { - deleteButton.style.display = 'none'; - } -}; - - -/** - * Redraw the drag areas. When an item (ranges only) is selected, - * it gets a drag area on the left and right side, to change its width - */ -links.Timeline.prototype.repaintDragAreas = function () { - var timeline = this, - options = this.options, - dom = this.dom, - frame = this.dom.items.frame; - - // create left drag area - var dragLeft = dom.items.dragLeft; - if (!dragLeft) { - dragLeft = document.createElement("DIV"); - dragLeft.className="timeline-event-range-drag-left"; - dragLeft.style.position = "absolute"; - - frame.appendChild(dragLeft); - dom.items.dragLeft = dragLeft; - } - - // create right drag area - var dragRight = dom.items.dragRight; - if (!dragRight) { - dragRight = document.createElement("DIV"); - dragRight.className="timeline-event-range-drag-right"; - dragRight.style.position = "absolute"; - - frame.appendChild(dragRight); - dom.items.dragRight = dragRight; - } - - // reposition left and right drag area - var index = this.selection ? this.selection.index : -1, - item = this.selection ? this.items[index] : undefined; - if (item && item.rendered && this.isEditable(item) && - (item instanceof links.Timeline.ItemRange)) { - var left = this.timeToScreen(item.start), - right = this.timeToScreen(item.end), - top = item.top, - height = item.height; - - dragLeft.style.left = left + 'px'; - dragLeft.style.top = top + 'px'; - dragLeft.style.width = options.dragAreaWidth + "px"; - dragLeft.style.height = height + 'px'; - dragLeft.style.display = ''; - frame.removeChild(dragLeft); - frame.appendChild(dragLeft); - - dragRight.style.left = (right - options.dragAreaWidth) + 'px'; - dragRight.style.top = top + 'px'; - dragRight.style.width = options.dragAreaWidth + "px"; - dragRight.style.height = height + 'px'; - dragRight.style.display = ''; - frame.removeChild(dragRight); - frame.appendChild(dragRight); - } - else { - dragLeft.style.display = 'none'; - dragRight.style.display = 'none'; - } -}; - -/** - * Create the navigation buttons for zooming and moving - */ -links.Timeline.prototype.repaintNavigation = function () { - var timeline = this, - options = this.options, - dom = this.dom, - frame = dom.frame, - navBar = dom.navBar; - - if (!navBar) { - var showButtonNew = options.showButtonNew && options.editable; - var showNavigation = options.showNavigation && (options.zoomable || options.moveable); - if (showNavigation || showButtonNew) { - // create a navigation bar containing the navigation buttons - navBar = document.createElement("DIV"); - navBar.style.position = "absolute"; - navBar.className = "timeline-navigation"; - if (options.groupsOnRight) { - navBar.style.left = '10px'; - } - else { - navBar.style.right = '10px'; - } - if (options.axisOnTop) { - navBar.style.bottom = '10px'; - } - else { - navBar.style.top = '10px'; - } - dom.navBar = navBar; - frame.appendChild(navBar); - } - - if (showButtonNew) { - // create a new in button - navBar.addButton = document.createElement("DIV"); - navBar.addButton.className = "timeline-navigation-new"; - - navBar.addButton.title = options.CREATE_NEW_EVENT; - var onAdd = function(event) { - links.Timeline.preventDefault(event); - links.Timeline.stopPropagation(event); - - // create a new event at the center of the frame - var w = timeline.size.contentWidth; - var x = w / 2; - var xstart = timeline.screenToTime(x - w / 10); // subtract 10% of timeline width - var xend = timeline.screenToTime(x + w / 10); // add 10% of timeline width - if (options.snapEvents) { - timeline.step.snap(xstart); - timeline.step.snap(xend); - } - - var content = options.NEW; - var group = timeline.groups.length ? timeline.groups[0].content : undefined; - var preventRender = true; - timeline.addItem({ - 'start': xstart, - 'end': xend, - 'content': content, - 'group': group - }, preventRender); - var index = (timeline.items.length - 1); - timeline.selectItem(index); - - timeline.applyAdd = true; - - // fire an add event. - // Note that the change can be canceled from within an event listener if - // this listener calls the method cancelAdd(). - timeline.trigger('add'); - - if (timeline.applyAdd) { - // render and select the item - timeline.render({animate: false}); - timeline.selectItem(index); - } - else { - // undo an add - timeline.deleteItem(index); - } - }; - links.Timeline.addEventListener(navBar.addButton, "mousedown", onAdd); - navBar.appendChild(navBar.addButton); - } - - if (showButtonNew && showNavigation) { - // create a separator line - navBar.addButton.style.borderRightWidth = "1px"; - navBar.addButton.style.borderRightStyle = "solid"; - } - - if (showNavigation) { - if (options.zoomable) { - // create a zoom in button - navBar.zoomInButton = document.createElement("DIV"); - navBar.zoomInButton.className = "timeline-navigation-zoom-in"; - navBar.zoomInButton.title = this.options.ZOOM_IN; - var onZoomIn = function(event) { - links.Timeline.preventDefault(event); - links.Timeline.stopPropagation(event); - timeline.zoom(0.4); - timeline.trigger("rangechange"); - timeline.trigger("rangechanged"); - }; - links.Timeline.addEventListener(navBar.zoomInButton, "mousedown", onZoomIn); - navBar.appendChild(navBar.zoomInButton); - - // create a zoom out button - navBar.zoomOutButton = document.createElement("DIV"); - navBar.zoomOutButton.className = "timeline-navigation-zoom-out"; - navBar.zoomOutButton.title = this.options.ZOOM_OUT; - var onZoomOut = function(event) { - links.Timeline.preventDefault(event); - links.Timeline.stopPropagation(event); - timeline.zoom(-0.4); - timeline.trigger("rangechange"); - timeline.trigger("rangechanged"); - }; - links.Timeline.addEventListener(navBar.zoomOutButton, "mousedown", onZoomOut); - navBar.appendChild(navBar.zoomOutButton); - } - - if (options.moveable) { - // create a move left button - navBar.moveLeftButton = document.createElement("DIV"); - navBar.moveLeftButton.className = "timeline-navigation-move-left"; - navBar.moveLeftButton.title = this.options.MOVE_LEFT; - var onMoveLeft = function(event) { - links.Timeline.preventDefault(event); - links.Timeline.stopPropagation(event); - timeline.move(-0.2); - timeline.trigger("rangechange"); - timeline.trigger("rangechanged"); - }; - links.Timeline.addEventListener(navBar.moveLeftButton, "mousedown", onMoveLeft); - navBar.appendChild(navBar.moveLeftButton); - - // create a move right button - navBar.moveRightButton = document.createElement("DIV"); - navBar.moveRightButton.className = "timeline-navigation-move-right"; - navBar.moveRightButton.title = this.options.MOVE_RIGHT; - var onMoveRight = function(event) { - links.Timeline.preventDefault(event); - links.Timeline.stopPropagation(event); - timeline.move(0.2); - timeline.trigger("rangechange"); - timeline.trigger("rangechanged"); - }; - links.Timeline.addEventListener(navBar.moveRightButton, "mousedown", onMoveRight); - navBar.appendChild(navBar.moveRightButton); - } - } - } -}; - - -/** - * Set current time. This function can be used to set the time in the client - * timeline equal with the time on a server. - * @param {Date} time - */ -links.Timeline.prototype.setCurrentTime = function(time) { - var now = new Date(); - this.clientTimeOffset = (time.valueOf() - now.valueOf()); - - this.repaintCurrentTime(); -}; - -/** - * Get current time. The time can have an offset from the real time, when - * the current time has been changed via the method setCurrentTime. - * @return {Date} time - */ -links.Timeline.prototype.getCurrentTime = function() { - var now = new Date(); - return new Date(now.valueOf() + this.clientTimeOffset); -}; - - -/** - * Set custom time. - * The custom time bar can be used to display events in past or future. - * @param {Date} time - */ -links.Timeline.prototype.setCustomTime = function(time) { - this.customTime = new Date(time.valueOf()); - this.repaintCustomTime(); -}; - -/** - * Retrieve the current custom time. - * @return {Date} customTime - */ -links.Timeline.prototype.getCustomTime = function() { - return new Date(this.customTime.valueOf()); -}; - -/** - * Set a custom scale. Autoscaling will be disabled. - * For example setScale(SCALE.MINUTES, 5) will result - * in minor steps of 5 minutes, and major steps of an hour. - * - * @param {links.Timeline.StepDate.SCALE} scale - * A scale. Choose from SCALE.MILLISECOND, - * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR, - * SCALE.WEEKDAY, SCALE.DAY, SCALE.MONTH, - * SCALE.YEAR. - * @param {int} step A step size, by default 1. Choose for - * example 1, 2, 5, or 10. - */ -links.Timeline.prototype.setScale = function(scale, step) { - this.step.setScale(scale, step); - this.render(); // TODO: optimize: only reflow/repaint axis -}; - -/** - * Enable or disable autoscaling - * @param {boolean} enable If true or not defined, autoscaling is enabled. - * If false, autoscaling is disabled. - */ -links.Timeline.prototype.setAutoScale = function(enable) { - this.step.setAutoScale(enable); - this.render(); // TODO: optimize: only reflow/repaint axis -}; - -/** - * Redraw the timeline - * Reloads the (linked) data table and redraws the timeline when resized. - * See also the method checkResize - */ -links.Timeline.prototype.redraw = function() { - this.setData(this.data); -}; - - -/** - * Check if the timeline is resized, and if so, redraw the timeline. - * Useful when the webpage is resized. - */ -links.Timeline.prototype.checkResize = function() { - // TODO: re-implement the method checkResize, or better, make it redundant as this.render will be smarter - this.render(); -}; - -/** - * Check whether a given item is editable - * @param {links.Timeline.Item} item - * @return {boolean} editable - */ -links.Timeline.prototype.isEditable = function (item) { - if (item) { - if (item.editable != undefined) { - return item.editable; - } - else { - return this.options.editable; - } - } - return false; -}; - -/** - * Calculate the factor and offset to convert a position on screen to the - * corresponding date and vice versa. - * After the method calcConversionFactor is executed once, the methods screenToTime and - * timeToScreen can be used. - */ -links.Timeline.prototype.recalcConversion = function() { - this.conversion.offset = this.start.valueOf(); - this.conversion.factor = this.size.contentWidth / - (this.end.valueOf() - this.start.valueOf()); -}; - - -/** - * Convert a position on screen (pixels) to a datetime - * Before this method can be used, the method calcConversionFactor must be - * executed once. - * @param {int} x Position on the screen in pixels - * @return {Date} time The datetime the corresponds with given position x - */ -links.Timeline.prototype.screenToTime = function(x) { - var conversion = this.conversion; - return new Date(x / conversion.factor + conversion.offset); -}; - -/** - * Convert a datetime (Date object) into a position on the screen - * Before this method can be used, the method calcConversionFactor must be - * executed once. - * @param {Date} time A date - * @return {int} x The position on the screen in pixels which corresponds - * with the given date. - */ -links.Timeline.prototype.timeToScreen = function(time) { - var conversion = this.conversion; - return (time.valueOf() - conversion.offset) * conversion.factor; -}; - - - -/** - * Event handler for touchstart event on mobile devices - */ -links.Timeline.prototype.onTouchStart = function(event) { - var params = this.eventParams, - me = this; - - if (params.touchDown) { - // if already moving, return - return; - } - - params.touchDown = true; - params.zoomed = false; - - this.onMouseDown(event); - - if (!params.onTouchMove) { - params.onTouchMove = function (event) {me.onTouchMove(event);}; - links.Timeline.addEventListener(document, "touchmove", params.onTouchMove); - } - if (!params.onTouchEnd) { - params.onTouchEnd = function (event) {me.onTouchEnd(event);}; - links.Timeline.addEventListener(document, "touchend", params.onTouchEnd); - } - - /* TODO - // check for double tap event - var delta = 500; // ms - var doubleTapStart = (new Date()).valueOf(); - var target = links.Timeline.getTarget(event); - var doubleTapItem = this.getItemIndex(target); - if (params.doubleTapStart && - (doubleTapStart - params.doubleTapStart) < delta && - doubleTapItem == params.doubleTapItem) { - delete params.doubleTapStart; - delete params.doubleTapItem; - me.onDblClick(event); - params.touchDown = false; - } - params.doubleTapStart = doubleTapStart; - params.doubleTapItem = doubleTapItem; - */ - // store timing for double taps - var target = links.Timeline.getTarget(event); - var item = this.getItemIndex(target); - params.doubleTapStartPrev = params.doubleTapStart; - params.doubleTapStart = (new Date()).valueOf(); - params.doubleTapItemPrev = params.doubleTapItem; - params.doubleTapItem = item; - - links.Timeline.preventDefault(event); -}; - -/** - * Event handler for touchmove event on mobile devices - */ -links.Timeline.prototype.onTouchMove = function(event) { - var params = this.eventParams; - - if (event.scale && event.scale !== 1) { - params.zoomed = true; - } - - if (!params.zoomed) { - // move - this.onMouseMove(event); - } - else { - if (this.options.zoomable) { - // pinch - // TODO: pinch only supported on iPhone/iPad. Create something manually for Android? - params.zoomed = true; - - var scale = event.scale, - oldWidth = (params.end.valueOf() - params.start.valueOf()), - newWidth = oldWidth / scale, - diff = newWidth - oldWidth, - start = new Date(parseInt(params.start.valueOf() - diff/2)), - end = new Date(parseInt(params.end.valueOf() + diff/2)); - - // TODO: determine zoom-around-date from touch positions? - - this.setVisibleChartRange(start, end); - this.trigger("rangechange"); - } - } - - links.Timeline.preventDefault(event); -}; - -/** - * Event handler for touchend event on mobile devices - */ -links.Timeline.prototype.onTouchEnd = function(event) { - var params = this.eventParams; - var me = this; - params.touchDown = false; - - if (params.zoomed) { - this.trigger("rangechanged"); - } - - if (params.onTouchMove) { - links.Timeline.removeEventListener(document, "touchmove", params.onTouchMove); - delete params.onTouchMove; - - } - if (params.onTouchEnd) { - links.Timeline.removeEventListener(document, "touchend", params.onTouchEnd); - delete params.onTouchEnd; - } - - this.onMouseUp(event); - - // check for double tap event - var delta = 500; // ms - var doubleTapEnd = (new Date()).valueOf(); - var target = links.Timeline.getTarget(event); - var doubleTapItem = this.getItemIndex(target); - if (params.doubleTapStartPrev && - (doubleTapEnd - params.doubleTapStartPrev) < delta && - params.doubleTapItem == params.doubleTapItemPrev) { - params.touchDown = true; - me.onDblClick(event); - params.touchDown = false; - } - - links.Timeline.preventDefault(event); -}; - - -/** - * Start a moving operation inside the provided parent element - * @param {Event} event The event that occurred (required for - * retrieving the mouse position) - */ -links.Timeline.prototype.onMouseDown = function(event) { - event = event || window.event; - - var params = this.eventParams, - options = this.options, - dom = this.dom; - - // only react on left mouse button down - var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1); - if (!leftButtonDown && !params.touchDown) { - return; - } - - // get mouse position - params.mouseX = links.Timeline.getPageX(event); - params.mouseY = links.Ti