BoxItem.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. var Item = require('./Item');
  2. /**
  3. * @constructor BoxItem
  4. * @extends Item
  5. * @param {Object} data Object containing parameters start
  6. * content, className.
  7. * @param {{toScreen: function, toTime: function}} conversion
  8. * Conversion functions from time to screen and vice versa
  9. * @param {Object} [options] Configuration options
  10. * // TODO: describe available options
  11. */
  12. function BoxItem (data, conversion, options) {
  13. this.props = {
  14. dot: {
  15. width: 0,
  16. height: 0
  17. },
  18. line: {
  19. width: 0,
  20. height: 0
  21. }
  22. };
  23. this.options = options;
  24. // validate data
  25. if (data) {
  26. if (data.start == undefined) {
  27. throw new Error('Property "start" missing in item ' + data);
  28. }
  29. }
  30. Item.call(this, data, conversion, options);
  31. }
  32. BoxItem.prototype = new Item (null, null, null);
  33. /**
  34. * Check whether this item is visible inside given range
  35. * @param {{start: number, end: number}} range with a timestamp for start and end
  36. * @returns {boolean} True if visible
  37. */
  38. BoxItem.prototype.isVisible = function(range) {
  39. // determine visibility
  40. var isVisible;
  41. var align = this.options.align;
  42. var widthInMs = this.width * range.getMillisecondsPerPixel();
  43. if (align == 'right') {
  44. isVisible = (this.data.start.getTime() > range.start ) && (this.data.start.getTime() - widthInMs < range.end);
  45. }
  46. else if (align == 'left') {
  47. isVisible = (this.data.start.getTime() + widthInMs > range.start ) && (this.data.start.getTime() < range.end);
  48. }
  49. else {
  50. // default or 'center'
  51. isVisible = (this.data.start.getTime() + widthInMs/2 > range.start ) && (this.data.start.getTime() - widthInMs/2 < range.end);
  52. }
  53. return isVisible;
  54. };
  55. BoxItem.prototype._createDomElement = function() {
  56. if (!this.dom) {
  57. // create DOM
  58. this.dom = {};
  59. // create main box
  60. this.dom.box = document.createElement('DIV');
  61. // contents box (inside the background box). used for making margins
  62. this.dom.content = document.createElement('DIV');
  63. this.dom.content.className = 'vis-item-content';
  64. this.dom.box.appendChild(this.dom.content);
  65. // line to axis
  66. this.dom.line = document.createElement('DIV');
  67. this.dom.line.className = 'vis-line';
  68. // dot on axis
  69. this.dom.dot = document.createElement('DIV');
  70. this.dom.dot.className = 'vis-dot';
  71. // attach this item as attribute
  72. this.dom.box['timeline-item'] = this;
  73. this.dirty = true;
  74. }
  75. }
  76. BoxItem.prototype._appendDomElement = function() {
  77. if (!this.parent) {
  78. throw new Error('Cannot redraw item: no parent attached');
  79. }
  80. if (!this.dom.box.parentNode) {
  81. var foreground = this.parent.dom.foreground;
  82. if (!foreground) throw new Error('Cannot redraw item: parent has no foreground container element');
  83. foreground.appendChild(this.dom.box);
  84. }
  85. if (!this.dom.line.parentNode) {
  86. var background = this.parent.dom.background;
  87. if (!background) throw new Error('Cannot redraw item: parent has no background container element');
  88. background.appendChild(this.dom.line);
  89. }
  90. if (!this.dom.dot.parentNode) {
  91. var axis = this.parent.dom.axis;
  92. if (!background) throw new Error('Cannot redraw item: parent has no axis container element');
  93. axis.appendChild(this.dom.dot);
  94. }
  95. this.displayed = true;
  96. }
  97. BoxItem.prototype._updateDirtyDomComponents = function() {
  98. // An item is marked dirty when:
  99. // - the item is not yet rendered
  100. // - the item's data is changed
  101. // - the item is selected/deselected
  102. if (this.dirty) {
  103. this._updateContents(this.dom.content);
  104. this._updateDataAttributes(this.dom.box);
  105. this._updateStyle(this.dom.box);
  106. var editable = (this.editable.updateTime || this.editable.updateGroup);
  107. // update class
  108. var className = (this.data.className? ' ' + this.data.className : '') +
  109. (this.selected ? ' vis-selected' : '') +
  110. (editable ? ' vis-editable' : ' vis-readonly');
  111. this.dom.box.className = 'vis-item vis-box' + className;
  112. this.dom.line.className = 'vis-item vis-line' + className;
  113. this.dom.dot.className = 'vis-item vis-dot' + className;
  114. }
  115. }
  116. BoxItem.prototype._getDomComponentsSizes = function() {
  117. return {
  118. previous: {
  119. right: this.dom.box.style.right,
  120. left: this.dom.box.style.left
  121. },
  122. dot: {
  123. height: this.dom.dot.offsetHeight,
  124. width: this.dom.dot.offsetWidth
  125. },
  126. line: {
  127. width: this.dom.line.offsetWidth
  128. },
  129. box: {
  130. width: this.dom.box.offsetWidth,
  131. height: this.dom.box.offsetHeight
  132. }
  133. }
  134. }
  135. BoxItem.prototype._updateDomComponentsSizes = function(sizes) {
  136. if (this.options.rtl) {
  137. this.dom.box.style.right = "0px";
  138. } else {
  139. this.dom.box.style.left = "0px";
  140. }
  141. // recalculate size
  142. this.props.dot.height = sizes.dot.height;
  143. this.props.dot.width = sizes.dot.width;
  144. this.props.line.width = sizes.line.width;
  145. this.width = sizes.box.width;
  146. this.height = sizes.box.height;
  147. // restore previous position
  148. if (this.options.rtl) {
  149. this.dom.box.style.right = sizes.previous.right;
  150. } else {
  151. this.dom.box.style.left = sizes.previous.left;
  152. }
  153. this.dirty = false;
  154. }
  155. BoxItem.prototype._repaintDomAdditionals = function() {
  156. this._repaintOnItemUpdateTimeTooltip(this.dom.box);
  157. this._repaintDragCenter();
  158. this._repaintDeleteButton(this.dom.box);
  159. }
  160. /**
  161. * Repaint the item
  162. * @param {boolean} [returnQueue=false] return the queue
  163. * @return {boolean} the redraw queue if returnQueue=true
  164. */
  165. BoxItem.prototype.redraw = function(returnQueue) {
  166. var sizes
  167. var queue = [
  168. // create item DOM
  169. this._createDomElement.bind(this),
  170. // append DOM to parent DOM
  171. this._appendDomElement.bind(this),
  172. // update dirty DOM
  173. this._updateDirtyDomComponents.bind(this),
  174. (function() {
  175. if (this.dirty) {
  176. sizes = this._getDomComponentsSizes();
  177. }
  178. }).bind(this),
  179. (function() {
  180. if (this.dirty) {
  181. this._updateDomComponentsSizes.bind(this)(sizes);
  182. }
  183. }).bind(this),
  184. // repaint DOM additionals
  185. this._repaintDomAdditionals.bind(this)
  186. ];
  187. if (returnQueue) {
  188. return queue;
  189. } else {
  190. var result;
  191. queue.forEach(function (fn) {
  192. result = fn();
  193. });
  194. return result;
  195. }
  196. };
  197. /**
  198. * Show the item in the DOM (when not already displayed). The items DOM will
  199. * be created when needed.
  200. */
  201. BoxItem.prototype.show = function() {
  202. if (!this.displayed) {
  203. this.redraw();
  204. }
  205. };
  206. /**
  207. * Hide the item from the DOM (when visible)
  208. */
  209. BoxItem.prototype.hide = function() {
  210. if (this.displayed) {
  211. var dom = this.dom;
  212. if (dom.box.parentNode) dom.box.parentNode.removeChild(dom.box);
  213. if (dom.line.parentNode) dom.line.parentNode.removeChild(dom.line);
  214. if (dom.dot.parentNode) dom.dot.parentNode.removeChild(dom.dot);
  215. this.displayed = false;
  216. }
  217. };
  218. /**
  219. * Reposition the item horizontally
  220. * @Override
  221. */
  222. BoxItem.prototype.repositionX = function() {
  223. var start = this.conversion.toScreen(this.data.start);
  224. var align = this.options.align;
  225. // calculate left position of the box
  226. if (align == 'right') {
  227. if (this.options.rtl) {
  228. this.right = start - this.width;
  229. // reposition box, line, and dot
  230. this.dom.box.style.right = this.right + 'px';
  231. this.dom.line.style.right = (start - this.props.line.width) + 'px';
  232. this.dom.dot.style.right = (start - this.props.line.width / 2 - this.props.dot.width / 2) + 'px';
  233. } else {
  234. this.left = start - this.width;
  235. // reposition box, line, and dot
  236. this.dom.box.style.left = this.left + 'px';
  237. this.dom.line.style.left = (start - this.props.line.width) + 'px';
  238. this.dom.dot.style.left = (start - this.props.line.width / 2 - this.props.dot.width / 2) + 'px';
  239. }
  240. }
  241. else if (align == 'left') {
  242. if (this.options.rtl) {
  243. this.right = start;
  244. // reposition box, line, and dot
  245. this.dom.box.style.right = this.right + 'px';
  246. this.dom.line.style.right = start + 'px';
  247. this.dom.dot.style.right = (start + this.props.line.width / 2 - this.props.dot.width / 2) + 'px';
  248. } else {
  249. this.left = start;
  250. // reposition box, line, and dot
  251. this.dom.box.style.left = this.left + 'px';
  252. this.dom.line.style.left = start + 'px';
  253. this.dom.dot.style.left = (start + this.props.line.width / 2 - this.props.dot.width / 2) + 'px';
  254. }
  255. }
  256. else {
  257. // default or 'center'
  258. if (this.options.rtl) {
  259. this.right = start - this.width / 2;
  260. // reposition box, line, and dot
  261. this.dom.box.style.right = this.right + 'px';
  262. this.dom.line.style.right = (start - this.props.line.width) + 'px';
  263. this.dom.dot.style.right = (start - this.props.dot.width / 2) + 'px';
  264. } else {
  265. this.left = start - this.width / 2;
  266. // reposition box, line, and dot
  267. this.dom.box.style.left = this.left + 'px';
  268. this.dom.line.style.left = (start - this.props.line.width / 2) + 'px';
  269. this.dom.dot.style.left = (start - this.props.dot.width / 2) + 'px';
  270. }
  271. }
  272. };
  273. /**
  274. * Reposition the item vertically
  275. * @Override
  276. */
  277. BoxItem.prototype.repositionY = function() {
  278. var orientation = this.options.orientation.item;
  279. var box = this.dom.box;
  280. var line = this.dom.line;
  281. var dot = this.dom.dot;
  282. if (orientation == 'top') {
  283. box.style.top = (this.top || 0) + 'px';
  284. line.style.top = '0';
  285. line.style.height = (this.parent.top + this.top + 1) + 'px';
  286. line.style.bottom = '';
  287. }
  288. else { // orientation 'bottom'
  289. var itemSetHeight = this.parent.itemSet.props.height; // TODO: this is nasty
  290. var lineHeight = itemSetHeight - this.parent.top - this.parent.height + this.top;
  291. box.style.top = (this.parent.height - this.top - this.height || 0) + 'px';
  292. line.style.top = (itemSetHeight - lineHeight) + 'px';
  293. line.style.bottom = '0';
  294. }
  295. dot.style.top = (-this.props.dot.height / 2) + 'px';
  296. };
  297. /**
  298. * Return the width of the item left from its start date
  299. * @return {number}
  300. */
  301. BoxItem.prototype.getWidthLeft = function () {
  302. return this.width / 2;
  303. };
  304. /**
  305. * Return the width of the item right from its start date
  306. * @return {number}
  307. */
  308. BoxItem.prototype.getWidthRight = function () {
  309. return this.width / 2;
  310. };
  311. module.exports = BoxItem;