12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384 |
- var Hammer = require('../../module/hammer');
- var util = require('../../util');
- var DataSet = require('../../DataSet');
- var DataView = require('../../DataView');
- var TimeStep = require('../TimeStep');
- var Component = require('./Component');
- var Group = require('./Group');
- var BackgroundGroup = require('./BackgroundGroup');
- var BoxItem = require('./item/BoxItem');
- var PointItem = require('./item/PointItem');
- var RangeItem = require('./item/RangeItem');
- var BackgroundItem = require('./item/BackgroundItem');
- var Popup = require('../../shared/Popup').default;
- var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
- var BACKGROUND = '__background__'; // reserved group id for background items without group
- /**
- * An ItemSet holds a set of items and ranges which can be displayed in a
- * range. The width is determined by the parent of the ItemSet, and the height
- * is determined by the size of the items.
- * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
- * @param {Object} [options] See ItemSet.setOptions for the available options.
- * @constructor ItemSet
- * @extends Component
- */
- function ItemSet(body, options) {
- this.body = body;
- this.defaultOptions = {
- type: null, // 'box', 'point', 'range', 'background'
- orientation: {
- item: 'bottom' // item orientation: 'top' or 'bottom'
- },
- align: 'auto', // alignment of box items
- stack: true,
- stackSubgroups: true,
- groupOrderSwap: function(fromGroup, toGroup, groups) { // eslint-disable-line no-unused-vars
- var targetOrder = toGroup.order;
- toGroup.order = fromGroup.order;
- fromGroup.order = targetOrder;
- },
- groupOrder: 'order',
- selectable: true,
- multiselect: false,
- itemsAlwaysDraggable: {
- item: false,
- range: false,
- },
- editable: {
- updateTime: false,
- updateGroup: false,
- add: false,
- remove: false,
- overrideItems: false
- },
- groupEditable: {
- order: false,
- add: false,
- remove: false
- },
- snap: TimeStep.snap,
- // Only called when `objectData.target === 'item'.
- onDropObjectOnItem: function(objectData, item, callback) {
- callback(item)
- },
- onAdd: function (item, callback) {
- callback(item);
- },
- onUpdate: function (item, callback) {
- callback(item);
- },
- onMove: function (item, callback) {
- callback(item);
- },
- onRemove: function (item, callback) {
- callback(item);
- },
- onMoving: function (item, callback) {
- callback(item);
- },
- onAddGroup: function (item, callback) {
- callback(item);
- },
- onMoveGroup: function (item, callback) {
- callback(item);
- },
- onRemoveGroup: function (item, callback) {
- callback(item);
- },
- margin: {
- item: {
- horizontal: 10,
- vertical: 10
- },
- axis: 20
- },
- showTooltips: true,
- tooltip: {
- followMouse: false,
- overflowMethod: 'flip'
- },
- tooltipOnItemUpdateTime: false
- };
- // options is shared by this ItemSet and all its items
- this.options = util.extend({}, this.defaultOptions);
- this.options.rtl = options.rtl;
- // options for getting items from the DataSet with the correct type
- this.itemOptions = {
- type: {start: 'Date', end: 'Date'}
- };
- this.conversion = {
- toScreen: body.util.toScreen,
- toTime: body.util.toTime
- };
- this.dom = {};
- this.props = {};
- this.hammer = null;
- var me = this;
- this.itemsData = null; // DataSet
- this.groupsData = null; // DataSet
- // listeners for the DataSet of the items
- this.itemListeners = {
- 'add': function (event, params, senderId) { // eslint-disable-line no-unused-vars
- me._onAdd(params.items);
- },
- 'update': function (event, params, senderId) { // eslint-disable-line no-unused-vars
- me._onUpdate(params.items);
- },
- 'remove': function (event, params, senderId) { // eslint-disable-line no-unused-vars
- me._onRemove(params.items);
- }
- };
- // listeners for the DataSet of the groups
- this.groupListeners = {
- 'add': function (event, params, senderId) { // eslint-disable-line no-unused-vars
- me._onAddGroups(params.items);
-
- if (me.groupsData && me.groupsData.length > 0) {
- var groupsData = me.groupsData.getDataSet();
- groupsData.get().forEach(function (groupData) {
- if (groupData.nestedGroups) {
- if (groupData.showNested != false) {
- groupData.showNested = true;
- }
- var updatedGroups = [];
- groupData.nestedGroups.forEach(function(nestedGroupId) {
- var updatedNestedGroup = groupsData.get(nestedGroupId);
- if (!updatedNestedGroup) { return; }
- updatedNestedGroup.nestedInGroup = groupData.id;
- if (groupData.showNested == false) {
- updatedNestedGroup.visible = false;
- }
- updatedGroups = updatedGroups.concat(updatedNestedGroup);
- });
- groupsData.update(updatedGroups, senderId);
- }
- });
- }
- },
- 'update': function (event, params, senderId) { // eslint-disable-line no-unused-vars
- me._onUpdateGroups(params.items);
- },
- 'remove': function (event, params, senderId) { // eslint-disable-line no-unused-vars
- me._onRemoveGroups(params.items);
- }
- };
- this.items = {}; // object with an Item for every data item
- this.groups = {}; // Group object for every group
- this.groupIds = [];
- this.selection = []; // list with the ids of all selected nodes
- this.popup = null;
- this.touchParams = {}; // stores properties while dragging
- this.groupTouchParams = {};
- // create the HTML DOM
- this._create();
- this.setOptions(options);
- }
- ItemSet.prototype = new Component();
- // available item types will be registered here
- ItemSet.types = {
- background: BackgroundItem,
- box: BoxItem,
- range: RangeItem,
- point: PointItem
- };
- /**
- * Create the HTML DOM for the ItemSet
- */
- ItemSet.prototype._create = function(){
- var frame = document.createElement('div');
- frame.className = 'vis-itemset';
- frame['timeline-itemset'] = this;
- this.dom.frame = frame;
- // create background panel
- var background = document.createElement('div');
- background.className = 'vis-background';
- frame.appendChild(background);
- this.dom.background = background;
- // create foreground panel
- var foreground = document.createElement('div');
- foreground.className = 'vis-foreground';
- frame.appendChild(foreground);
- this.dom.foreground = foreground;
- // create axis panel
- var axis = document.createElement('div');
- axis.className = 'vis-axis';
- this.dom.axis = axis;
- // create labelset
- var labelSet = document.createElement('div');
- labelSet.className = 'vis-labelset';
- this.dom.labelSet = labelSet;
- // create ungrouped Group
- this._updateUngrouped();
- // create background Group
- var backgroundGroup = new BackgroundGroup(BACKGROUND, null, this);
- backgroundGroup.show();
- this.groups[BACKGROUND] = backgroundGroup;
- // attach event listeners
- // Note: we bind to the centerContainer for the case where the height
- // of the center container is larger than of the ItemSet, so we
- // can click in the empty area to create a new item or deselect an item.
- this.hammer = new Hammer(this.body.dom.centerContainer);
- // drag items when selected
- this.hammer.on('hammer.input', function (event) {
- if (event.isFirst) {
- this._onTouch(event);
- }
- }.bind(this));
- 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_HORIZONTAL});
- // single select (or unselect) when tapping an item
- this.hammer.on('tap', this._onSelectItem.bind(this));
- // multi select when holding mouse/touch, or on ctrl+click
- this.hammer.on('press', this._onMultiSelectItem.bind(this));
- // add item on doubletap
- this.hammer.on('doubletap', this._onAddItem.bind(this));
- if (this.options.rtl) {
- this.groupHammer = new Hammer(this.body.dom.rightContainer);
- } else {
- this.groupHammer = new Hammer(this.body.dom.leftContainer);
- }
-
- this.groupHammer.on('tap', this._onGroupClick.bind(this));
- this.groupHammer.on('panstart', this._onGroupDragStart.bind(this));
- this.groupHammer.on('panmove', this._onGroupDrag.bind(this));
- this.groupHammer.on('panend', this._onGroupDragEnd.bind(this));
- this.groupHammer.get('pan').set({threshold:5, direction: Hammer.DIRECTION_VERTICAL});
-
- this.body.dom.centerContainer.addEventListener('mouseover', this._onMouseOver.bind(this));
- this.body.dom.centerContainer.addEventListener('mouseout', this._onMouseOut.bind(this));
- this.body.dom.centerContainer.addEventListener('mousemove', this._onMouseMove.bind(this));
- // right-click on timeline
- this.body.dom.centerContainer.addEventListener('contextmenu', this._onDragEnd.bind(this));
- this.body.dom.centerContainer.addEventListener('mousewheel', this._onMouseWheel.bind(this));
- // attach to the DOM
- this.show();
- };
- /**
- * Set options for the ItemSet. Existing options will be extended/overwritten.
- * @param {Object} [options] The following options are available:
- * {string} type
- * Default type for the items. Choose from 'box'
- * (default), 'point', 'range', or 'background'.
- * The default style can be overwritten by
- * individual items.
- * {string} align
- * Alignment for the items, only applicable for
- * BoxItem. Choose 'center' (default), 'left', or
- * 'right'.
- * {string} orientation.item
- * Orientation of the item set. Choose 'top' or
- * 'bottom' (default).
- * {Function} groupOrder
- * A sorting function for ordering groups
- * {boolean} stack
- * If true (default), items will be stacked on
- * top of each other.
- * {number} margin.axis
- * Margin between the axis and the items in pixels.
- * Default is 20.
- * {number} margin.item.horizontal
- * Horizontal margin between items in pixels.
- * Default is 10.
- * {number} margin.item.vertical
- * Vertical Margin between items in pixels.
- * Default is 10.
- * {number} margin.item
- * Margin between items in pixels in both horizontal
- * and vertical direction. Default is 10.
- * {number} margin
- * Set margin for both axis and items in pixels.
- * {boolean} selectable
- * If true (default), items can be selected.
- * {boolean} multiselect
- * If true, multiple items can be selected.
- * False by default.
- * {boolean} editable
- * Set all editable options to true or false
- * {boolean} editable.updateTime
- * Allow dragging an item to an other moment in time
- * {boolean} editable.updateGroup
- * Allow dragging an item to an other group
- * {boolean} editable.add
- * Allow creating new items on double tap
- * {boolean} editable.remove
- * Allow removing items by clicking the delete button
- * top right of a selected item.
- * {Function(item: Item, callback: Function)} onAdd
- * Callback function triggered when an item is about to be added:
- * when the user double taps an empty space in the Timeline.
- * {Function(item: Item, callback: Function)} onUpdate
- * Callback function fired when an item is about to be updated.
- * This function typically has to show a dialog where the user
- * change the item. If not implemented, nothing happens.
- * {Function(item: Item, callback: Function)} onMove
- * Fired when an item has been moved. If not implemented,
- * the move action will be accepted.
- * {Function(item: Item, callback: Function)} onRemove
- * Fired when an item is about to be deleted.
- * If not implemented, the item will be always removed.
- */
- ItemSet.prototype.setOptions = function(options) {
- if (options) {
- // copy all options that we know
- var fields = [
- 'type', 'rtl', 'align', 'order', 'stack', 'stackSubgroups', 'selectable', 'multiselect',
- 'multiselectPerGroup', 'groupOrder', 'dataAttributes', 'template', 'groupTemplate', 'visibleFrameTemplate',
- 'hide', 'snap', 'groupOrderSwap', 'showTooltips', 'tooltip', 'tooltipOnItemUpdateTime'
- ];
- util.selectiveExtend(fields, this.options, options);
- if ('itemsAlwaysDraggable' in options) {
- if (typeof options.itemsAlwaysDraggable === 'boolean') {
- this.options.itemsAlwaysDraggable.item = options.itemsAlwaysDraggable;
- this.options.itemsAlwaysDraggable.range = false;
- }
- else if (typeof options.itemsAlwaysDraggable === 'object') {
- util.selectiveExtend(['item', 'range'], this.options.itemsAlwaysDraggable, options.itemsAlwaysDraggable);
- // only allow range always draggable when item is always draggable as well
- if (! this.options.itemsAlwaysDraggable.item) {
- this.options.itemsAlwaysDraggable.range = false;
- }
- }
- }
- if ('orientation' in options) {
- if (typeof options.orientation === 'string') {
- this.options.orientation.item = options.orientation === 'top' ? 'top' : 'bottom';
- }
- else if (typeof options.orientation === 'object' && 'item' in options.orientation) {
- this.options.orientation.item = options.orientation.item;
- }
- }
- if ('margin' in options) {
- if (typeof options.margin === 'number') {
- this.options.margin.axis = options.margin;
- this.options.margin.item.horizontal = options.margin;
- this.options.margin.item.vertical = options.margin;
- }
- else if (typeof options.margin === 'object') {
- util.selectiveExtend(['axis'], this.options.margin, options.margin);
- if ('item' in options.margin) {
- if (typeof options.margin.item === 'number') {
- this.options.margin.item.horizontal = options.margin.item;
- this.options.margin.item.vertical = options.margin.item;
- }
- else if (typeof options.margin.item === 'object') {
- util.selectiveExtend(['horizontal', 'vertical'], this.options.margin.item, options.margin.item);
- }
- }
- }
- }
- if ('editable' in options) {
- if (typeof options.editable === 'boolean') {
- this.options.editable.updateTime = options.editable;
- this.options.editable.updateGroup = options.editable;
- this.options.editable.add = options.editable;
- this.options.editable.remove = options.editable;
- this.options.editable.overrideItems = false;
- }
- else if (typeof options.editable === 'object') {
- util.selectiveExtend(['updateTime', 'updateGroup', 'add', 'remove', 'overrideItems'], this.options.editable, options.editable);
- }
- }
- if ('groupEditable' in options) {
- if (typeof options.groupEditable === 'boolean') {
- this.options.groupEditable.order = options.groupEditable;
- this.options.groupEditable.add = options.groupEditable;
- this.options.groupEditable.remove = options.groupEditable;
- }
- else if (typeof options.groupEditable === 'object') {
- util.selectiveExtend(['order', 'add', 'remove'], this.options.groupEditable, options.groupEditable);
- }
- }
- // callback functions
- var addCallback = (function (name) {
- var fn = options[name];
- if (fn) {
- if (!(fn instanceof Function)) {
- throw new Error('option ' + name + ' must be a function ' + name + '(item, callback)');
- }
- this.options[name] = fn;
- }
- }).bind(this);
- ['onDropObjectOnItem', 'onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving', 'onAddGroup', 'onMoveGroup', 'onRemoveGroup'].forEach(addCallback);
- // force the itemSet to refresh: options like orientation and margins may be changed
- this.markDirty();
- }
- };
- /**
- * Mark the ItemSet dirty so it will refresh everything with next redraw.
- * Optionally, all items can be marked as dirty and be refreshed.
- * @param {{refreshItems: boolean}} [options]
- */
- ItemSet.prototype.markDirty = function(options) {
- this.groupIds = [];
- if (options && options.refreshItems) {
- util.forEach(this.items, function (item) {
- item.dirty = true;
- if (item.displayed) item.redraw();
- });
- }
- };
- /**
- * Destroy the ItemSet
- */
- ItemSet.prototype.destroy = function() {
- this.hide();
- this.setItems(null);
- this.setGroups(null);
- this.hammer = null;
- this.body = null;
- this.conversion = null;
- };
- /**
- * Hide the component from the DOM
- */
- ItemSet.prototype.hide = function() {
- // remove the frame containing the items
- if (this.dom.frame.parentNode) {
- this.dom.frame.parentNode.removeChild(this.dom.frame);
- }
- // remove the axis with dots
- if (this.dom.axis.parentNode) {
- this.dom.axis.parentNode.removeChild(this.dom.axis);
- }
- // remove the labelset containing all group labels
- if (this.dom.labelSet.parentNode) {
- this.dom.labelSet.parentNode.removeChild(this.dom.labelSet);
- }
- };
- /**
- * Show the component in the DOM (when not already visible).
- */
- ItemSet.prototype.show = function() {
- // show frame containing the items
- if (!this.dom.frame.parentNode) {
- this.body.dom.center.appendChild(this.dom.frame);
- }
- // show axis with dots
- if (!this.dom.axis.parentNode) {
- this.body.dom.backgroundVertical.appendChild(this.dom.axis);
- }
- // show labelset containing labels
- if (!this.dom.labelSet.parentNode) {
- if (this.options.rtl) {
- this.body.dom.right.appendChild(this.dom.labelSet);
- } else {
- this.body.dom.left.appendChild(this.dom.labelSet);
- }
- }
- };
- /**
- * Set selected items by their id. Replaces the current selection
- * Unknown id's are silently ignored.
- * @param {string[] | string} [ids] An array with zero or more id's of the items to be
- * selected, or a single item id. If ids is undefined
- * or an empty array, all items will be unselected.
- */
- ItemSet.prototype.setSelection = function(ids) {
- var i, ii, id, item;
- if (ids == undefined) ids = [];
- if (!Array.isArray(ids)) ids = [ids];
- // unselect currently selected items
- for (i = 0, ii = this.selection.length; i < ii; i++) {
- id = this.selection[i];
- item = this.items[id];
- if (item) item.unselect();
- }
- // select items
- this.selection = [];
- for (i = 0, ii = ids.length; i < ii; i++) {
- id = ids[i];
- item = this.items[id];
- if (item) {
- this.selection.push(id);
- item.select();
- }
- }
- };
- /**
- * Get the selected items by their id
- * @return {Array} ids The ids of the selected items
- */
- ItemSet.prototype.getSelection = function() {
- return this.selection.concat([]);
- };
- /**
- * Get the id's of the currently visible items.
- * @returns {Array} The ids of the visible items
- */
- ItemSet.prototype.getVisibleItems = function() {
- var range = this.body.range.getRange();
- var right, left;
- if (this.options.rtl) {
- right = this.body.util.toScreen(range.start);
- left = this.body.util.toScreen(range.end);
- } else {
- left = this.body.util.toScreen(range.start);
- right = this.body.util.toScreen(range.end);
- }
- var ids = [];
- for (var groupId in this.groups) {
- if (this.groups.hasOwnProperty(groupId)) {
- var group = this.groups[groupId];
- var rawVisibleItems = group.isVisible ? group.visibleItems : [];
- // filter the "raw" set with visibleItems into a set which is really
- // visible by pixels
- for (var i = 0; i < rawVisibleItems.length; i++) {
- var item = rawVisibleItems[i];
- // TODO: also check whether visible vertically
- if (this.options.rtl) {
- if ((item.right < left) && (item.right + item.width > right)) {
- ids.push(item.id);
- }
- } else {
- if ((item.left < right) && (item.left + item.width > left)) {
- ids.push(item.id);
- }
- }
- }
- }
- }
- return ids;
- };
- /**
- * Deselect a selected item
- * @param {string | number} id
- * @private
- */
- ItemSet.prototype._deselect = function(id) {
- var selection = this.selection;
- for (var i = 0, ii = selection.length; i < ii; i++) {
- if (selection[i] == id) { // non-strict comparison!
- selection.splice(i, 1);
- break;
- }
- }
- };
- /**
- * Repaint the component
- * @return {boolean} Returns true if the component is resized
- */
- ItemSet.prototype.redraw = function() {
- var margin = this.options.margin,
- range = this.body.range,
- asSize = util.option.asSize,
- options = this.options,
- orientation = options.orientation.item,
- resized = false,
- frame = this.dom.frame;
- // recalculate absolute position (before redrawing groups)
- this.props.top = this.body.domProps.top.height + this.body.domProps.border.top;
- if (this.options.rtl) {
- this.props.right = this.body.domProps.right.width + this.body.domProps.border.right;
- } else {
- this.props.left = this.body.domProps.left.width + this.body.domProps.border.left;
- }
- // update class name
- frame.className = 'vis-itemset';
- // reorder the groups (if needed)
- resized = this._orderGroups() || resized;
- // check whether zoomed (in that case we need to re-stack everything)
- // TODO: would be nicer to get this as a trigger from Range
- var visibleInterval = range.end - range.start;
- var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.props.width != this.props.lastWidth);
- var scrolled = range.start != this.lastRangeStart;
- var changedStackOption = options.stack != this.lastStack;
- var changedStackSubgroupsOption = options.stackSubgroups != this.lastStackSubgroups;
- var forceRestack = (zoomed || scrolled || changedStackOption || changedStackSubgroupsOption);
- this.lastVisibleInterval = visibleInterval;
- this.lastRangeStart = range.start;
- this.lastStack = options.stack;
- this.lastStackSubgroups = options.stackSubgroups;
- this.props.lastWidth = this.props.width;
- var firstGroup = this._firstGroup();
- var firstMargin = {
- item: margin.item,
- axis: margin.axis
- };
- var nonFirstMargin = {
- item: margin.item,
- axis: margin.item.vertical / 2
- };
- var height = 0;
- var minHeight = margin.axis + margin.item.vertical;
- // redraw the background group
- this.groups[BACKGROUND].redraw(range, nonFirstMargin, forceRestack);
- var redrawQueue = {};
- var redrawQueueLength = 0;
- // collect redraw functions
- util.forEach(this.groups, function (group, key) {
- if (key === BACKGROUND) return;
- var groupMargin = group == firstGroup ? firstMargin : nonFirstMargin;
- var returnQueue = true;
- redrawQueue[key] = group.redraw(range, groupMargin, forceRestack, returnQueue);
- redrawQueueLength = redrawQueue[key].length;
- });
- var needRedraw = redrawQueueLength > 0;
- if (needRedraw) {
- var redrawResults = {};
- for (var i = 0; i < redrawQueueLength; i++) {
- util.forEach(redrawQueue, function (fns, key) {
- redrawResults[key] = fns[i]();
- });
- }
- // redraw all regular groups
- util.forEach(this.groups, function (group, key) {
- if (key === BACKGROUND) return;
- var groupResized = redrawResults[key];
- resized = groupResized || resized;
- height += group.height;
- });
- height = Math.max(height, minHeight);
- }
- height = Math.max(height, minHeight);
- // update frame height
- frame.style.height = asSize(height);
- // calculate actual size
- this.props.width = frame.offsetWidth;
- this.props.height = height;
- // reposition axis
- this.dom.axis.style.top = asSize((orientation == 'top') ?
- (this.body.domProps.top.height + this.body.domProps.border.top) :
- (this.body.domProps.top.height + this.body.domProps.centerContainer.height));
- if (this.options.rtl) {
- this.dom.axis.style.right = '0';
- } else {
- this.dom.axis.style.left = '0';
- }
- this.initialItemSetDrawn = true;
- // check if this component is resized
- resized = this._isResized() || resized;
- return resized;
- };
- /**
- * Get the first group, aligned with the axis
- * @return {Group | null} firstGroup
- * @private
- */
- ItemSet.prototype._firstGroup = function() {
- var firstGroupIndex = (this.options.orientation.item == 'top') ? 0 : (this.groupIds.length - 1);
- var firstGroupId = this.groupIds[firstGroupIndex];
- var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED];
- return firstGroup || null;
- };
- /**
- * Create or delete the group holding all ungrouped items. This group is used when
- * there are no groups specified.
- * @protected
- */
- ItemSet.prototype._updateUngrouped = function() {
- var ungrouped = this.groups[UNGROUPED];
- var item, itemId;
- if (this.groupsData) {
- // remove the group holding all ungrouped items
- if (ungrouped) {
- ungrouped.hide();
- delete this.groups[UNGROUPED];
- for (itemId in this.items) {
- if (this.items.hasOwnProperty(itemId)) {
- item = this.items[itemId];
- item.parent && item.parent.remove(item);
- var groupId = this._getGroupId(item.data);
- var group = this.groups[groupId];
- group && group.add(item) || item.hide();
- }
- }
- }
- }
- else {
- // create a group holding all (unfiltered) items
- if (!ungrouped) {
- var id = null;
- var data = null;
- ungrouped = new Group(id, data, this);
- this.groups[UNGROUPED] = ungrouped;
- for (itemId in this.items) {
- if (this.items.hasOwnProperty(itemId)) {
- item = this.items[itemId];
- ungrouped.add(item);
- }
- }
- ungrouped.show();
- }
- }
- };
- /**
- * Get the element for the labelset
- * @return {HTMLElement} labelSet
- */
- ItemSet.prototype.getLabelSet = function() {
- return this.dom.labelSet;
- };
- /**
- * Set items
- * @param {vis.DataSet | null} items
- */
- ItemSet.prototype.setItems = function(items) {
- var me = this,
- ids,
- oldItemsData = this.itemsData;
- // replace the dataset
- if (!items) {
- this.itemsData = null;
- }
- else if (items instanceof DataSet || items instanceof DataView) {
- this.itemsData = items;
- }
- else {
- throw new TypeError('Data must be an instance of DataSet or DataView');
- }
- if (oldItemsData) {
- // unsubscribe from old dataset
- util.forEach(this.itemListeners, function (callback, event) {
- oldItemsData.off(event, callback);
- });
- // remove all drawn items
- ids = oldItemsData.getIds();
- this._onRemove(ids);
- }
- if (this.itemsData) {
- // subscribe to new dataset
- var id = this.id;
- util.forEach(this.itemListeners, function (callback, event) {
- me.itemsData.on(event, callback, id);
- });
- // add all new items
- ids = this.itemsData.getIds();
- this._onAdd(ids);
- // update the group holding all ungrouped items
- this._updateUngrouped();
- }
- this.body.emitter.emit('_change', {queue: true});
- };
- /**
- * Get the current items
- * @returns {vis.DataSet | null}
- */
- ItemSet.prototype.getItems = function() {
- return this.itemsData;
- };
- /**
- * Set groups
- * @param {vis.DataSet} groups
- */
- ItemSet.prototype.setGroups = function(groups) {
- var me = this,
- ids;
- // unsubscribe from current dataset
- if (this.groupsData) {
- util.forEach(this.groupListeners, function (callback, event) {
- me.groupsData.off(event, callback);
- });
- // remove all drawn groups
- ids = this.groupsData.getIds();
- this.groupsData = null;
- this._onRemoveGroups(ids); // note: this will cause a redraw
- }
- // replace the dataset
- if (!groups) {
- this.groupsData = null;
- }
- else if (groups instanceof DataSet || groups instanceof DataView) {
- this.groupsData = groups;
- }
- else {
- throw new TypeError('Data must be an instance of DataSet or DataView');
- }
- if (this.groupsData) {
- // go over all groups nesting
- var groupsData = this.groupsData;
- if (this.groupsData instanceof DataView) {
- groupsData = this.groupsData.getDataSet()
- }
- groupsData.get().forEach(function(group){
- if (group.nestedGroups) {
- group.nestedGroups.forEach(function(nestedGroupId) {
- var updatedNestedGroup = groupsData.get(nestedGroupId);
- updatedNestedGroup.nestedInGroup = group.id;
- if (group.showNested == false) {
- updatedNestedGroup.visible = false;
- }
- groupsData.update(updatedNestedGroup);
- })
- }
- });
- // subscribe to new dataset
- var id = this.id;
- util.forEach(this.groupListeners, function (callback, event) {
- me.groupsData.on(event, callback, id);
- });
- // draw all ms
- ids = this.groupsData.getIds();
- this._onAddGroups(ids);
- }
- // update the group holding all ungrouped items
- this._updateUngrouped();
- // update the order of all items in each group
- this._order();
- this.body.emitter.emit('_change', {queue: true});
- };
- /**
- * Get the current groups
- * @returns {vis.DataSet | null} groups
- */
- ItemSet.prototype.getGroups = function() {
- return this.groupsData;
- };
- /**
- * Remove an item by its id
- * @param {string | number} id
- */
- ItemSet.prototype.removeItem = function(id) {
- var item = this.itemsData.get(id),
- dataset = this.itemsData.getDataSet();
- if (item) {
- // confirm deletion
- this.options.onRemove(item, function (item) {
- if (item) {
- // remove by id here, it is possible that an item has no id defined
- // itself, so better not delete by the item itself
- dataset.remove(id);
- }
- });
- }
- };
- /**
- * Get the time of an item based on it's data and options.type
- * @param {Object} itemData
- * @returns {string} Returns the type
- * @private
- */
- ItemSet.prototype._getType = function (itemData) {
- return itemData.type || this.options.type || (itemData.end ? 'range' : 'box');
- };
- /**
- * Get the group id for an item
- * @param {Object} itemData
- * @returns {string} Returns the groupId
- * @private
- */
- ItemSet.prototype._getGroupId = function (itemData) {
- var type = this._getType(itemData);
- if (type == 'background' && itemData.group == undefined) {
- return BACKGROUND;
- }
- else {
- return this.groupsData ? itemData.group : UNGROUPED;
- }
- };
- /**
- * Handle updated items
- * @param {number[]} ids
- * @protected
- */
- ItemSet.prototype._onUpdate = function(ids) {
- var me = this;
- ids.forEach(function (id) {
- var itemData = me.itemsData.get(id, me.itemOptions);
- var item = me.items[id];
- var type = itemData ? me._getType(itemData) : null;
- var constructor = ItemSet.types[type];
- var selected;
- if (item) {
- // update item
- if (!constructor || !(item instanceof constructor)) {
- // item type has changed, delete the item and recreate it
- selected = item.selected; // preserve selection of this item
- me._removeItem(item);
- item = null;
- }
- else {
- me._updateItem(item, itemData);
- }
- }
- if (!item && itemData) {
- // create item
- if (constructor) {
- item = new constructor(itemData, me.conversion, me.options);
- item.id = id; // TODO: not so nice setting id afterwards
- me._addItem(item);
- if (selected) {
- this.selection.push(id);
- item.select();
- }
- }
- else if (type == 'rangeoverflow') {
- // TODO: deprecated since version 2.1.0 (or 3.0.0?). cleanup some day
- throw new TypeError('Item type "rangeoverflow" is deprecated. Use css styling instead: ' +
- '.vis-item.vis-range .vis-item-content {overflow: visible;}');
- }
- else {
- throw new TypeError('Unknown item type "' + type + '"');
- }
- }
- }.bind(this));
- this._order();
- this.body.emitter.emit('_change', {queue: true});
- };
- /**
- * Handle added items
- * @param {number[]} ids
- * @protected
- */
- ItemSet.prototype._onAdd = ItemSet.prototype._onUpdate;
- /**
- * Handle removed items
- * @param {number[]} ids
- * @protected
- */
- ItemSet.prototype._onRemove = function(ids) {
- var count = 0;
- var me = this;
- ids.forEach(function (id) {
- var item = me.items[id];
- if (item) {
- count++;
- me._removeItem(item);
- }
- });
- if (count) {
- // update order
- this._order();
- this.body.emitter.emit('_change', {queue: true});
- }
- };
- /**
- * Update the order of item in all groups
- * @private
- */
- ItemSet.prototype._order = function() {
- // reorder the items in all groups
- // TODO: optimization: only reorder groups affected by the changed items
- util.forEach(this.groups, function (group) {
- group.order();
- });
- };
- /**
- * Handle updated groups
- * @param {number[]} ids
- * @private
- */
- ItemSet.prototype._onUpdateGroups = function(ids) {
- this._onAddGroups(ids);
- };
- /**
- * Handle changed groups (added or updated)
- * @param {number[]} ids
- * @private
- */
- ItemSet.prototype._onAddGroups = function(ids) {
- var me = this;
- ids.forEach(function (id) {
- var groupData = me.groupsData.get(id);
- var group = me.groups[id];
- if (!group) {
- // check for reserved ids
- if (id == UNGROUPED || id == BACKGROUND) {
- throw new Error('Illegal group id. ' + id + ' is a reserved id.');
- }
- var groupOptions = Object.create(me.options);
- util.extend(groupOptions, {
- height: null
- });
- group = new Group(id, groupData, me);
- me.groups[id] = group;
- // add items with this groupId to the new group
- for (var itemId in me.items) {
- if (me.items.hasOwnProperty(itemId)) {
- var item = me.items[itemId];
- if (item.data.group == id) {
- group.add(item);
- }
- }
- }
- group.order();
- group.show();
- }
- else {
- // update group
- group.setData(groupData);
- }
- });
- this.body.emitter.emit('_change', {queue: true});
- };
- /**
- * Handle removed groups
- * @param {number[]} ids
- * @private
- */
- ItemSet.prototype._onRemoveGroups = function(ids) {
- var groups = this.groups;
- ids.forEach(function (id) {
- var group = groups[id];
- if (group) {
- group.hide();
- delete groups[id];
- }
- });
- this.markDirty();
- this.body.emitter.emit('_change', {queue: true});
- };
- /**
- * Reorder the groups if needed
- * @return {boolean} changed
- * @private
- */
- ItemSet.prototype._orderGroups = function () {
- if (this.groupsData) {
- // reorder the groups
- var groupIds = this.groupsData.getIds({
- order: this.options.groupOrder
- });
- groupIds = this._orderNestedGroups(groupIds);
- var changed = !util.equalArray(groupIds, this.groupIds);
- if (changed) {
- // hide all groups, removes them from the DOM
- var groups = this.groups;
- groupIds.forEach(function (groupId) {
- groups[groupId].hide();
- });
- // show the groups again, attach them to the DOM in correct order
- groupIds.forEach(function (groupId) {
- groups[groupId].show();
- });
- this.groupIds = groupIds;
- }
- return changed;
- }
- else {
- return false;
- }
- };
- /**
- * Reorder the nested groups
- *
- * @param {Array.<number>} groupIds
- * @returns {Array.<number>}
- * @private
- */
- ItemSet.prototype._orderNestedGroups = function(groupIds) {
- var newGroupIdsOrder = [];
- groupIds.forEach(function(groupId){
- var groupData = this.groupsData.get(groupId);
- if (!groupData.nestedInGroup) {
- newGroupIdsOrder.push(groupId)
- }
- if (groupData.nestedGroups) {
- var nestedGroups = this.groupsData.get({
- filter: function(nestedGroup) {
- return nestedGroup.nestedInGroup == groupId;
- },
- order: this.options.groupOrder
- });
- var nestedGroupIds = nestedGroups.map(function(nestedGroup) { return nestedGroup.id });
- newGroupIdsOrder = newGroupIdsOrder.concat(nestedGroupIds);
- }
- }, this);
- return newGroupIdsOrder;
- };
- /**
- * Add a new item
- * @param {Item} item
- * @private
- */
- ItemSet.prototype._addItem = function(item) {
- this.items[item.id] = item;
- // add to group
- var groupId = this._getGroupId(item.data);
- var group = this.groups[groupId];
- if (!group) {
- item.groupShowing = false;
- } else if (group && group.data && group.data.showNested) {
- item.groupShowing = true;
- }
- if (group) group.add(item);
- };
- /**
- * Update an existing item
- * @param {Item} item
- * @param {Object} itemData
- * @private
- */
- ItemSet.prototype._updateItem = function(item, itemData) {
- // update the items data (will redraw the item when displayed)
- item.setData(itemData);
- var groupId = this._getGroupId(item.data);
- var group = this.groups[groupId];
- if (!group) {
- item.groupShowing = false;
- } else if (group && group.data && group.data.showNested) {
- item.groupShowing = true;
- }
- };
- /**
- * Delete an item from the ItemSet: remove it from the DOM, from the map
- * with items, and from the map with visible items, and from the selection
- * @param {Item} item
- * @private
- */
- ItemSet.prototype._removeItem = function(item) {
- // remove from DOM
- item.hide();
- // remove from items
- delete this.items[item.id];
- // remove from selection
- var index = this.selection.indexOf(item.id);
- if (index != -1) this.selection.splice(index, 1);
- // remove from group
- item.parent && item.parent.remove(item);
- };
- /**
- * Create an array containing all items being a range (having an end date)
- * @param {Array.<Object>} array
- * @returns {Array}
- * @private
- */
- ItemSet.prototype._constructByEndArray = function(array) {
- var endArray = [];
- for (var i = 0; i < array.length; i++) {
- if (array[i] instanceof RangeItem) {
- endArray.push(array[i]);
- }
- }
- return endArray;
- };
- /**
- * Register the clicked item on touch, before dragStart is initiated.
- *
- * dragStart is initiated from a mousemove event, AFTER the mouse/touch is
- * already moving. Therefore, the mouse/touch can sometimes be above an other
- * DOM element than the item itself.
- *
- * @param {Event} event
- * @private
- */
- ItemSet.prototype._onTouch = function (event) {
- // store the touched item, used in _onDragStart
- this.touchParams.item = this.itemFromTarget(event);
- this.touchParams.dragLeftItem = event.target.dragLeftItem || false;
- this.touchParams.dragRightItem = event.target.dragRightItem || false;
- this.touchParams.itemProps = null;
- };
- /**
- * Given an group id, returns the index it has.
- *
- * @param {number} groupId
- * @returns {number} index / groupId
- * @private
- */
- ItemSet.prototype._getGroupIndex = function(groupId) {
- for (var i = 0; i < this.groupIds.length; i++) {
- if (groupId == this.groupIds[i])
- return i;
- }
- };
- /**
- * Start dragging the selected events
- * @param {Event} event
- * @private
- */
- ItemSet.prototype._onDragStart = function (event) {
- if (this.touchParams.itemIsDragging) { return; }
- var item = this.touchParams.item || null;
- var me = this;
- var props;
- if (item && (item.selected || this.options.itemsAlwaysDraggable.item)) {
- if (this.options.editable.overrideItems &&
- !this.options.editable.updateTime &&
- !this.options.editable.updateGroup) {
- return;
- }
- // override options.editable
- if ((item.editable != null && !item.editable.updateTime && !item.editable.updateGroup)
- && !this.options.editable.overrideItems) {
- return;
- }
- var dragLeftItem = this.touchParams.dragLeftItem;
- var dragRightItem = this.touchParams.dragRightItem;
- this.touchParams.itemIsDragging = true;
- this.touchParams.selectedItem = item;
- if (dragLeftItem) {
- props = {
- item: dragLeftItem,
- initialX: event.center.x,
- dragLeft: true,
- data: this._cloneItemData(item.data)
- };
- this.touchParams.itemProps = [props];
- } else if (dragRightItem) {
- props = {
- item: dragRightItem,
- initialX: event.center.x,
- dragRight: true,
- data: this._cloneItemData(item.data)
- };
- this.touchParams.itemProps = [props];
- } else if (this.options.editable.add && (event.srcEvent.ctrlKey || event.srcEvent.metaKey)) {
- // create a new range item when dragging with ctrl key down
- this._onDragStartAddItem(event);
- } else {
- if(this.groupIds.length < 1) {
- // Mitigates a race condition if _onDragStart() is
- // called after markDirty() without redraw() being called between.
- this.redraw();
- }
-
- var baseGroupIndex = this._getGroupIndex(item.data.group);
- var itemsToDrag = (this.options.itemsAlwaysDraggable.item && !item.selected) ? [item.id] : this.getSelection();
- this.touchParams.itemProps = itemsToDrag.map(function (id) {
- var item = me.items[id];
- var groupIndex = me._getGroupIndex(item.data.group);
- return {
- item: item,
- initialX: event.center.x,
- groupOffset: baseGroupIndex-groupIndex,
- data: this._cloneItemData(item.data)
- };
- }.bind(this));
- }
- event.stopPropagation();
- } else if (this.options.editable.add && (event.srcEvent.ctrlKey || event.srcEvent.metaKey)) {
- // create a new range item when dragging with ctrl key down
- this._onDragStartAddItem(event);
- }
- };
- /**
- * Start creating a new range item by dragging.
- * @param {Event} event
- * @private
- */
- ItemSet.prototype._onDragStartAddItem = function (event) {
- var xAbs;
- var x;
- var snap = this.options.snap || null;
- if (this.options.rtl) {
- xAbs = util.getAbsoluteRight(this.dom.frame);
- x = xAbs - event.center.x + 10; // plus 10 to compensate for the drag starting as soon as you've moved 10px
- } else {
- xAbs = util.getAbsoluteLeft(this.dom.frame);
- x = event.center.x - xAbs - 10; // minus 10 to compensate for the drag starting as soon as you've moved 10px
- }
- var time = this.body.util.toTime(x);
- var scale = this.body.util.getScale();
- var step = this.body.util.getStep();
- var start = snap ? snap(time, scale, step) : time;
- var end = start;
- var itemData = {
- type: 'range',
- start: start,
- end: end,
- content: 'new item'
- };
- var id = util.randomUUID();
- itemData[this.itemsData._fieldId] = id;
- var group = this.groupFromTarget(event);
- if (group) {
- itemData.group = group.groupId;
- }
- var newItem = new RangeItem(itemData, this.conversion, this.options);
- newItem.id = id; // TODO: not so nice setting id afterwards
- newItem.data = this._cloneItemData(itemData);
- this._addItem(newItem);
- this.touchParams.selectedItem = newItem;
-
- var props = {
- item: newItem,
- initialX: event.center.x,
- data: newItem.data
- };
- if (this.options.rtl) {
- props.dragLeft = true;
- } else {
- props.dragRight = true;
- }
- this.touchParams.itemProps = [props];
- event.stopPropagation();
- };
- /**
- * Drag selected items
- * @param {Event} event
- * @private
- */
- ItemSet.prototype._onDrag = function (event) {
- if (this.touchParams.itemProps) {
- event.stopPropagation();
- var me = this;
- var snap = this.options.snap || null;
- var xOffset;
- if (this.options.rtl) {
- xOffset = this.body.dom.root.offsetLeft + this.body.domProps.right.width;
- } else {
- xOffset = this.body.dom.root.offsetLeft + this.body.domProps.left.width;
- }
- var scale = this.body.util.getScale();
- var step = this.body.util.getStep();
- //only calculate the new group for the item that's actually dragged
- var selectedItem = this.touchParams.selectedItem;
- var updateGroupAllowed = ((this.options.editable.overrideItems || selectedItem.editable == null) && this.options.editable.updateGroup) ||
- (!this.options.editable.overrideItems && selectedItem.editable != null && selectedItem.editable.updateGroup);
- var newGroupBase = null;
- if (updateGroupAllowed && selectedItem) {
- if (selectedItem.data.group != undefined) {
- // drag from one group to another
- var group = me.groupFromTarget(event);
- if (group) {
- //we know the offset for all items, so the new group for all items
- //will be relative to this one.
- newGroupBase = this._getGroupIndex(group.groupId);
- }
- }
- }
- // move
- this.touchParams.itemProps.forEach(function (props) {
- var current = me.body.util.toTime(event.center.x - xOffset);
- var initial = me.body.util.toTime(props.initialX - xOffset);
- var offset;
- var initialStart;
- var initialEnd;
- var start;
- var end;
- if (this.options.rtl) {
- offset = -(current - initial); // ms
- } else {
- offset = (current - initial); // ms
- }
- var itemData = this._cloneItemData(props.item.data); // clone the data
- if (props.item.editable != null
- && !props.item.editable.updateTime
- && !props.item.editable.updateGroup
- && !me.options.editable.overrideItems) {
- return;
- }
- var updateTimeAllowed = ((this.options.editable.overrideItems || selectedItem.editable == null) && this.options.editable.updateTime) ||
- (!this.options.editable.overrideItems && selectedItem.editable != null && selectedItem.editable.updateTime);
- if (updateTimeAllowed) {
- if (props.dragLeft) {
- // drag left side of a range item
- if (this.options.rtl) {
- if (itemData.end != undefined) {
- initialEnd = util.convert(props.data.end, 'Date');
- end = new Date(initialEnd.valueOf() + offset);
- // TODO: pass a Moment instead of a Date to snap(). (Breaking change)
- itemData.end = snap ? snap(end, scale, step) : end;
- }
- } else {
- if (itemData.start != undefined) {
- initialStart = util.convert(props.data.start, 'Date');
- start = new Date(initialStart.valueOf() + offset);
- // TODO: pass a Moment instead of a Date to snap(). (Breaking change)
- itemData.start = snap ? snap(start, scale, step) : start;
- }
- }
- }
- else if (props.dragRight) {
- // drag right side of a range item
- if (this.options.rtl) {
- if (itemData.start != undefined) {
- initialStart = util.convert(props.data.start, 'Date');
- start = new Date(initialStart.valueOf() + offset);
- // TODO: pass a Moment instead of a Date to snap(). (Breaking change)
- itemData.start = snap ? snap(start, scale, step) : start;
- }
- } else {
- if (itemData.end != undefined) {
- initialEnd = util.convert(props.data.end, 'Date');
- end = new Date(initialEnd.valueOf() + offset);
- // TODO: pass a Moment instead of a Date to snap(). (Breaking change)
- itemData.end = snap ? snap(end, scale, step) : end;
- }
- }
- }
- else {
- // drag both start and end
- if (itemData.start != undefined) {
- initialStart = util.convert(props.data.start, 'Date').valueOf();
- start = new Date(initialStart + offset);
- if (itemData.end != undefined) {
- initialEnd = util.convert(props.data.end, 'Date');
- var duration = initialEnd.valueOf() - initialStart.valueOf();
- // TODO: pass a Moment instead of a Date to snap(). (Breaking change)
- itemData.start = snap ? snap(start, scale, step) : start;
- itemData.end = new Date(itemData.start.valueOf() + duration);
- }
- else {
- // TODO: pass a Moment instead of a Date to snap(). (Breaking change)
- itemData.start = snap ? snap(start, scale, step) : start;
- }
- }
- }
- }
- if (updateGroupAllowed && (!props.dragLeft && !props.dragRight) && newGroupBase!=null) {
- if (itemData.group != undefined) {
- var newOffset = newGroupBase - props.groupOffset;
- //make sure we stay in bounds
- newOffset = Math.max(0, newOffset);
- newOffset = Math.min(me.groupIds.length-1, newOffset);
- itemData.group = me.groupIds[newOffset];
- }
- }
- // confirm moving the item
- itemData = this._cloneItemData(itemData); // convert start and end to the correct type
- me.options.onMoving(itemData, function (itemData) {
- if (itemData) {
- props.item.setData(this._cloneItemData(itemData, 'Date'));
- }
- }.bind(this));
- }.bind(this));
-
- this.body.emitter.emit('_change');
- }
- };
- /**
- * Move an item to another group
- * @param {Item} item
- * @param {string | number} groupId
- * @private
- */
- ItemSet.prototype._moveToGroup = function(item, groupId) {
- var group = this.groups[groupId];
- if (group && group.groupId != item.data.group) {
- var oldGroup = item.parent;
- oldGroup.remove(item);
- oldGroup.order();
-
- item.data.group = group.groupId;
-
- group.add(item);
- group.order();
- }
- };
- /**
- * End of dragging selected items
- * @param {Event} event
- * @private
- */
- ItemSet.prototype._onDragEnd = function (event) {
- this.touchParams.itemIsDragging = false;
- if (this.touchParams.itemProps) {
- event.stopPropagation();
- var me = this;
- var dataset = this.itemsData.getDataSet();
- var itemProps = this.touchParams.itemProps ;
- this.touchParams.itemProps = null;
- itemProps.forEach(function (props) {
- var id = props.item.id;
- var exists = me.itemsData.get(id, me.itemOptions) != null;
- if (!exists) {
- // add a new item
- me.options.onAdd(props.item.data, function (itemData) {
- me._removeItem(props.item); // remove temporary item
- if (itemData) {
- me.itemsData.getDataSet().add(itemData);
- }
- // force re-stacking of all items next redraw
- me.body.emitter.emit('_change');
- });
- }
- else {
- // update existing item
- var itemData = this._cloneItemData(props.item.data); // convert start and end to the correct type
- me.options.onMove(itemData, function (itemData) {
- if (itemData) {
- // apply changes
- itemData[dataset._fieldId] = id; // ensure the item contains its id (can be undefined)
- dataset.update(itemData);
- }
- else {
- // restore original values
- props.item.setData(props.data);
- me.body.emitter.emit('_change');
- }
- });
- }
- }.bind(this));
- }
- };
- ItemSet.prototype._onGroupClick = function (event) {
- var group = this.groupFromTarget(event);
- if (!group || !group.nestedGroups) return;
- var groupsData = this.groupsData.getDataSet();
- var nestingGroup = groupsData.get(group.groupId)
- if (nestingGroup.showNested == undefined) { nestingGroup.showNested = true; }
- nestingGroup.showNested = !nestingGroup.showNested;
- var nestedGroups = groupsData.get(group.nestedGroups).map(function(nestedGroup) {
- nestedGroup.visible = nestingGroup.showNested;
- return nestedGroup;
- });
- groupsData.update(nestedGroups.concat(nestingGroup));
- if (nestingGroup.showNested) {
- util.removeClassName(group.dom.label, 'collapsed');
- util.addClassName(group.dom.label, 'expanded');
- } else {
- util.removeClassName(group.dom.label, 'expanded');
- var collapsedDirClassName = this.options.rtl ? 'collapsed-rtl' : 'collapsed';
- util.addClassName(group.dom.label, collapsedDirClassName);
- }
- };
- ItemSet.prototype._onGroupDragStart = function (event) {
- if (this.options.groupEditable.order) {
- this.groupTouchParams.group = this.groupFromTarget(event);
-
- if (this.groupTouchParams.group) {
- event.stopPropagation();
-
- this.groupTouchParams.originalOrder = this.groupsData.getIds({
- order: this.options.groupOrder
- });
- }
- }
- };
- ItemSet.prototype._onGroupDrag = function (event) {
- if (this.options.groupEditable.order && this.groupTouchParams.group) {
- event.stopPropagation();
-
- var groupsData = this.groupsData;
- if (this.groupsData instanceof DataView) {
- groupsData = this.groupsData.getDataSet()
- }
- // drag from one group to another
- var group = this.groupFromTarget(event);
-
- // try to avoid toggling when groups differ in height
- if (group && group.height != this.groupTouchParams.group.height) {
- var movingUp = (group.top < this.groupTouchParams.group.top);
- var clientY = event.center ? event.center.y : event.clientY;
- var targetGroupTop = util.getAbsoluteTop(group.dom.foreground);
- var draggedGroupHeight = this.groupTouchParams.group.height;
- if (movingUp) {
- // skip swapping the groups when the dragged group is not below clientY afterwards
- if (targetGroupTop + draggedGroupHeight < clientY) {
- return;
- }
- } else {
- var targetGroupHeight = group.height;
- // skip swapping the groups when the dragged group is not below clientY afterwards
- if (targetGroupTop + targetGroupHeight - draggedGroupHeight > clientY) {
- return;
- }
- }
- }
-
- if (group && group != this.groupTouchParams.group) {
- var targetGroup = groupsData.get(group.groupId);
- var draggedGroup = groupsData.get(this.groupTouchParams.group.groupId);
-
- // switch groups
- if (draggedGroup && targetGroup) {
- this.options.groupOrderSwap(draggedGroup, targetGroup, groupsData);
- groupsData.update(draggedGroup);
- groupsData.update(targetGroup);
- }
-
- // fetch current order of groups
- var newOrder = groupsData.getIds({
- order: this.options.groupOrder
- });
-
- // in case of changes since _onGroupDragStart
- if (!util.equalArray(newOrder, this.groupTouchParams.originalOrder)) {
- var origOrder = this.groupTouchParams.originalOrder;
- var draggedId = this.groupTouchParams.group.groupId;
- var numGroups = Math.min(origOrder.length, newOrder.length);
- var curPos = 0;
- var newOffset = 0;
- var orgOffset = 0;
- while (curPos < numGroups) {
- // as long as the groups are where they should be step down along the groups order
- while ((curPos+newOffset) < numGroups
- && (curPos+orgOffset) < numGroups
- && newOrder[curPos+newOffset] == origOrder[curPos+orgOffset]) {
- curPos++;
- }
-
- // all ok
- if (curPos+newOffset >= numGroups) {
- break;
- }
-
- // not all ok
- // if dragged group was move upwards everything below should have an offset
- if (newOrder[curPos+newOffset] == draggedId) {
- newOffset = 1;
- }
- // if dragged group was move downwards everything above should have an offset
- else if (origOrder[curPos+orgOffset] == draggedId) {
- orgOffset = 1;
- }
- // found a group (apart from dragged group) that has the wrong position -> switch with the
- // group at the position where other one should be, fix index arrays and continue
- else {
- var slippedPosition = newOrder.indexOf(origOrder[curPos+orgOffset]);
- var switchGroup = groupsData.get(newOrder[curPos+newOffset]);
- var shouldBeGroup = groupsData.get(origOrder[curPos+orgOffset]);
- this.options.groupOrderSwap(switchGroup, shouldBeGroup, groupsData);
- groupsData.update(switchGroup);
- groupsData.update(shouldBeGroup);
-
- var switchGroupId = newOrder[curPos+newOffset];
- newOrder[curPos+newOffset] = origOrder[curPos+orgOffset];
- newOrder[slippedPosition] = switchGroupId;
-
- curPos++;
- }
- }
- }
-
- }
- }
- };
- ItemSet.prototype._onGroupDragEnd = function (event) {
- if (this.options.groupEditable.order && this.groupTouchParams.group) {
- event.stopPropagation();
-
- // update existing group
- var me = this;
- var id = me.groupTouchParams.group.groupId;
- var dataset = me.groupsData.getDataSet();
- var groupData = util.extend({}, dataset.get(id)); // clone the data
- me.options.onMoveGroup(groupData, function (groupData) {
- if (groupData) {
- // apply changes
- groupData[dataset._fieldId] = id; // ensure the group contains its id (can be undefined)
- dataset.update(groupData);
- }
- else {
- // fetch current order of groups
- var newOrder = dataset.getIds({
- order: me.options.groupOrder
- });
- // restore original order
- if (!util.equalArray(newOrder, me.groupTouchParams.originalOrder)) {
- var origOrder = me.groupTouchParams.originalOrder;
- var numGroups = Math.min(origOrder.length, newOrder.length);
- var curPos = 0;
- while (curPos < numGroups) {
- // as long as the groups are where they should be step down along the groups order
- while (curPos < numGroups && newOrder[curPos] == origOrder[curPos]) {
- curPos++;
- }
- // all ok
- if (curPos >= numGroups) {
- break;
- }
- // found a group that has the wrong position -> switch with the
- // group at the position where other one should be, fix index arrays and continue
- var slippedPosition = newOrder.indexOf(origOrder[curPos]);
- var switchGroup = dataset.get(newOrder[curPos]);
- var shouldBeGroup = dataset.get(origOrder[curPos]);
- me.options.groupOrderSwap(switchGroup, shouldBeGroup, dataset);
- dataset.update(switchGroup);
- dataset.update(shouldBeGroup);
- var switchGroupId = newOrder[curPos];
- newOrder[curPos] = origOrder[curPos];
- newOrder[slippedPosition] = switchGroupId;
- curPos++;
- }
- }
- }
- });
- me.body.emitter.emit('groupDragged', { groupId: id });
- }
- };
- /**
- * Handle selecting/deselecting an item when tapping it
- * @param {Event} event
- * @private
- */
- ItemSet.prototype._onSelectItem = function (event) {
- if (!this.options.selectable) return;
- var ctrlKey = event.srcEvent && (event.srcEvent.ctrlKey || event.srcEvent.metaKey);
- var shiftKey = event.srcEvent && event.srcEvent.shiftKey;
- if (ctrlKey || shiftKey) {
- this._onMultiSelectItem(event);
- return;
- }
- var oldSelection = this.getSelection();
- var item = this.itemFromTarget(event);
- var selection = item ? [item.id] : [];
- this.setSelection(selection);
- var newSelection = this.getSelection();
- // emit a select event,
- // except when old selection is empty and new selection is still empty
- if (newSelection.length > 0 || oldSelection.length > 0) {
- this.body.emitter.emit('select', {
- items: newSelection,
- event: event
- });
- }
- };
- /**
- * Handle hovering an item
- * @param {Event} event
- * @private
- */
- ItemSet.prototype._onMouseOver = function (event) {
- var item = this.itemFromTarget(event);
- if (!item) return;
- // Item we just left
- var related = this.itemFromRelatedTarget(event);
- if (item === related) {
- // We haven't changed item, just element in the item
- return;
- }
- var title = item.getTitle();
- if (this.options.showTooltips && title) {
- if (this.popup == null) {
- this.popup = new Popup(this.body.dom.root,
- this.options.tooltip.overflowMethod || 'flip');
- }
- this.popup.setText(title);
- var container = this.body.dom.centerContainer;
- this.popup.setPosition(
- event.clientX - util.getAbsoluteLeft(container) + container.offsetLeft,
- event.clientY - util.getAbsoluteTop(container) + container.offsetTop
- );
- this.popup.show();
- } else {
- // Hovering over item without a title, hide popup
- // Needed instead of _just_ in _onMouseOut due to #2572
- if (this.popup != null) {
- this.popup.hide();
- }
- }
- this.body.emitter.emit('itemover', {
- item: item.id,
- event: event
- });
- };
- ItemSet.prototype._onMouseOut = function (event) {
- var item = this.itemFromTarget(event);
- if (!item) return;
- // Item we are going to
- var related = this.itemFromRelatedTarget(event);
- if (item === related) {
- // We aren't changing item, just element in the item
- return;
- }
- if (this.popup != null) {
- this.popup.hide();
- }
- this.body.emitter.emit('itemout', {
- item: item.id,
- event: event
- });
- };
- ItemSet.prototype._onMouseMove = function (event) {
- var item = this.itemFromTarget(event);
- if (!item) return;
- if (this.options.showTooltips && this.options.tooltip.followMouse) {
- if (this.popup) {
- if (!this.popup.hidden) {
- var container = this.body.dom.centerContainer;
- this.popup.setPosition(
- event.clientX - util.getAbsoluteLeft(container) + container.offsetLeft,
- event.clientY - util.getAbsoluteTop(container) + container.offsetTop
- );
- this.popup.show(); // Redraw
- }
- }
- }
- };
- /**
- * Handle mousewheel
- * @param {Event} event The event
- * @private
- */
- ItemSet.prototype._onMouseWheel = function(event) {
- if (this.touchParams.itemIsDragging) {
- this._onDragEnd(event);
- }
- };
- /**
- * Handle updates of an item on double tap
- * @param {vis.Item} item The item
- * @private
- */
- ItemSet.prototype._onUpdateItem = function (item) {
- if (!this.options.selectable) return;
- if (!this.options.editable.add) return;
- var me = this;
-
- if (item) {
- // execute async handler to update the item (or cancel it)
- var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset
- this.options.onUpdate(itemData, function (itemData) {
- if (itemData) {
- me.itemsData.getDataSet().update(itemData);
- }
- });
- }
- };
- /**
- * Handle drop event of data on item
- * Only called when `objectData.target === 'item'.
- * @param {Event} event The event
- * @private
- */
- ItemSet.prototype._onDropObjectOnItem = function(event) {
- var item = this.itemFromTarget(event);
- var objectData = JSON.parse(event.dataTransfer.getData("text"));
- this.options.onDropObjectOnItem(objectData, item)
- }
- /**
- * Handle creation of an item on double tap or drop of a drag event
- * @param {Event} event The event
- * @private
- */
- ItemSet.prototype._onAddItem = function (event) {
- if (!this.options.selectable) return;
- if (!this.options.editable.add) return;
- var me = this;
- var snap = this.options.snap || null;
- var xAbs;
- var x;
- // add item
- if (this.options.rtl) {
- xAbs = util.getAbsoluteRight(this.dom.frame);
- x = xAbs - event.center.x;
- } else {
- xAbs = util.getAbsoluteLeft(this.dom.frame);
- x = event.center.x - xAbs;
- }
- // var xAbs = util.getAbsoluteLeft(this.dom.frame);
- // var x = event.center.x - xAbs;
- var start = this.body.util.toTime(x);
- var scale = this.body.util.getScale();
- var step = this.body.util.getStep();
- var end;
- var newItemData;
- if (event.type == 'drop') {
- newItemData = JSON.parse(event.dataTransfer.getData("text"));
- newItemData.content = newItemData.content ? newItemData.content : 'new item';
- newItemData.start = newItemData.start ? newItemData.start : (snap ? snap(start, scale, step) : start);
- newItemData.type = newItemData.type || 'box';
- newItemData[this.itemsData._fieldId] = newItemData.id || util.randomUUID();
- if (newItemData.type == 'range' && !newItemData.end) {
- end = this.body.util.toTime(x + this.props.width / 5);
- newItemData.end = snap ? snap(end, scale, step) : end;
- }
- } else {
- newItemData = {
- start: snap ? snap(start, scale, step) : start,
- content: 'new item'
- };
- newItemData[this.itemsData._fieldId] = util.randomUUID();
- // when default type is a range, add a default end date to the new item
- if (this.options.type === 'range') {
- end = this.body.util.toTime(x + this.props.width / 5);
- newItemData.end = snap ? snap(end, scale, step) : end;
- }
- }
- var group = this.groupFromTarget(event);
- if (group) {
- newItemData.group = group.groupId;
- }
- // execute async handler to customize (or cancel) adding an item
- newItemData = this._cloneItemData(newItemData); // convert start and end to the correct type
- this.options.onAdd(newItemData, function (item) {
- if (item) {
- me.itemsData.getDataSet().add(item);
- if (event.type == 'drop') {
- me.setSelection([item.id]);
- }
- // TODO: need to trigger a redraw?
- }
- });
- };
- /**
- * Handle selecting/deselecting multiple items when holding an item
- * @param {Event} event
- * @private
- */
- ItemSet.prototype._onMultiSelectItem = function (event) {
- if (!this.options.selectable) return;
- var item = this.itemFromTarget(event);
- if (item) {
- // multi select items (if allowed)
- var selection = this.options.multiselect
- ? this.getSelection() // take current selection
- : []; // deselect current selection
- var shiftKey = event.srcEvent && event.srcEvent.shiftKey || false;
- if (shiftKey && this.options.multiselect) {
- // select all items between the old selection and the tapped item
- var itemGroup = this.itemsData.get(item.id).group;
- // when filtering get the group of the last selected item
- var lastSelectedGroup = undefined;
- if (this.options.multiselectPerGroup) {
- if (selection.length > 0) {
- lastSelectedGroup = this.itemsData.get(selection[0]).group;
- }
- }
- // determine the selection range
- if (!this.options.multiselectPerGroup || lastSelectedGroup == undefined || lastSelectedGroup == itemGroup) {
- selection.push(item.id);
- }
- var range = ItemSet._getItemRange(this.itemsData.get(selection, this.itemOptions));
-
- if (!this.options.multiselectPerGroup || lastSelectedGroup == itemGroup) {
- // select all items within the selection range
- selection = [];
- for (var id in this.items) {
- if (this.items.hasOwnProperty(id)) {
- var _item = this.items[id];
- var start = _item.data.start;
- var end = (_item.data.end !== undefined) ? _item.data.end : start;
- if (start >= range.min &&
- end <= range.max &&
- (!this.options.multiselectPerGroup || lastSelectedGroup == this.itemsData.get(_item.id).group) &&
- !(_item instanceof BackgroundItem)) {
- selection.push(_item.id); // do not use id but item.id, id itself is stringified
- }
- }
- }
- }
- }
- else {
- // add/remove this item from the current selection
- var index = selection.indexOf(item.id);
- if (index == -1) {
- // item is not yet selected -> select it
- selection.push(item.id);
- }
- else {
- // item is already selected -> deselect it
- selection.splice(index, 1);
- }
- }
- this.setSelection(selection);
- this.body.emitter.emit('select', {
- items: this.getSelection(),
- event: event
- });
- }
- };
- /**
- * Calculate the time range of a list of items
- * @param {Array.<Object>} itemsData
- * @return {{min: Date, max: Date}} Returns the range of the provided items
- * @private
- */
- ItemSet._getItemRange = function(itemsData) {
- var max = null;
- var min = null;
- itemsData.forEach(function (data) {
- if (min == null || data.start < min) {
- min = data.start;
- }
- if (data.end != undefined) {
- if (max == null || data.end > max) {
- max = data.end;
- }
- }
- else {
- if (max == null || data.start > max) {
- max = data.start;
- }
- }
- });
- return {
- min: min,
- max: max
- }
- };
- /**
- * Find an item from an element:
- * searches for the attribute 'timeline-item' in the element's tree
- * @param {HTMLElement} element
- * @return {Item | null} item
- */
- ItemSet.prototype.itemFromElement = function(element) {
- var cur = element;
- while (cur) {
- if (cur.hasOwnProperty('timeline-item')) {
- return cur['timeline-item'];
- }
- cur = cur.parentNode;
- }
- return null;
- };
- /**
- * Find an item from an event target:
- * searches for the attribute 'timeline-item' in the event target's element tree
- * @param {Event} event
- * @return {Item | null} item
- */
- ItemSet.prototype.itemFromTarget = function(event) {
- return this.itemFromElement(event.target);
- };
- /**
- * Find an item from an event's related target:
- * searches for the attribute 'timeline-item' in the related target's element tree
- * @param {Event} event
- * @return {Item | null} item
- */
- ItemSet.prototype.itemFromRelatedTarget = function(event) {
- return this.itemFromElement(event.relatedTarget);
- };
- /**
- * Find the Group from an event target:
- * searches for the attribute 'timeline-group' in the event target's element tree
- * @param {Event} event
- * @return {Group | null} group
- */
- ItemSet.prototype.groupFromTarget = function(event) {
- var clientY = event.center ? event.center.y : event.clientY;
- var groupIds = this.groupIds;
-
- if (groupIds.length <= 0 && this.groupsData) {
- groupIds = this.groupsData.getIds({
- order: this.options.groupOrder
- });
- }
-
- for (var i = 0; i < groupIds.length; i++) {
- var groupId = groupIds[i];
- var group = this.groups[groupId];
- var foreground = group.dom.foreground;
- var top = util.getAbsoluteTop(foreground);
- if (clientY > top && clientY < top + foreground.offsetHeight) {
- return group;
- }
- if (this.options.orientation.item === 'top') {
- if (i === this.groupIds.length - 1 && clientY > top) {
- return group;
- }
- }
- else {
- if (i === 0 && clientY < top + foreground.offset) {
- return group;
- }
- }
- }
- return null;
- };
- /**
- * Find the ItemSet from an event target:
- * searches for the attribute 'timeline-itemset' in the event target's element tree
- * @param {Event} event
- * @return {ItemSet | null} item
- */
- ItemSet.itemSetFromTarget = function(event) {
- var target = event.target;
- while (target) {
- if (target.hasOwnProperty('timeline-itemset')) {
- return target['timeline-itemset'];
- }
- target = target.parentNode;
- }
- return null;
- };
- /**
- * Clone the data of an item, and "normalize" it: convert the start and end date
- * to the type (Date, Moment, ...) configured in the DataSet. If not configured,
- * start and end are converted to Date.
- * @param {Object} itemData, typically `item.data`
- * @param {string} [type] Optional Date type. If not provided, the type from the DataSet is taken
- * @return {Object} The cloned object
- * @private
- */
- ItemSet.prototype._cloneItemData = function (itemData, type) {
- var clone = util.extend({}, itemData);
- if (!type) {
- // convert start and end date to the type (Date, Moment, ...) configured in the DataSet
- type = this.itemsData.getDataSet()._options.type;
- }
- if (clone.start != undefined) {
- clone.start = util.convert(clone.start, type && type.start || 'Date');
- }
- if (clone.end != undefined) {
- clone.end = util.convert(clone.end , type && type.end || 'Date');
- }
- return clone;
- };
- module.exports = ItemSet;
|