TimelineHighlightIndicatorView.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: Storytelling
  5. * (C) Copyright IBM Corp. 2017, 2020
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. */
  8. define(['baglass/core-client/js/core-client/ui/core/View', 'jquery', 'gemini/app/util/ScreenReaderUtil', 'storytelling-ui/storytelling-ui.min', 'text!./templates/TimelineHighlightIndicatorView.html', 'dashboard-analytics/visualizations/renderer/filter/FilterLabelHelper', './highlight/TimelineHighlightFlyoutController', 'react', 'react-dom', '../util/WidgetHelper'], function (View, $, ScreenReaderUtil, StorytellingUI, Template, FilterLabelHelper, TimelineHighlightFlyoutController, React, ReactDOM, WidgetHelper) {
  9. var Indicator = View.extend({
  10. templateString: Template,
  11. //@override
  12. /**
  13. * @param options.$el {object} - JQuery node to append the highlight HTML
  14. * @param options.widgetId {string} - the widget Id of the selected timeline widget
  15. * @param options.scaleManager {object} - scaleManager of the timeline
  16. * @param options.episodeModel {object} - episodeModel of the selected timeline widget
  17. * @param options.highlightAct {object} - optional, the existing highlightAct model
  18. */
  19. init: function init(options) {
  20. Indicator.inherited('init', this, arguments);
  21. this.controller = options.controller;
  22. this.widgetId = options.widgetId;
  23. this.scaleManager = options.scaleManager;
  24. this.episodeModel = options.episodeModel;
  25. this.highlightAct = options.highlightAct;
  26. this.id = options.highlightAct.id;
  27. this.services = options.services;
  28. this.dashboardApi = options.dashboardApi;
  29. this.widgetHelper = new WidgetHelper({ dashboardApi: this.dashboardApi });
  30. this.widget = this.widgetHelper.getWidget(this.widgetId);
  31. this.content = this.widgetHelper.getContent(this.widgetId);
  32. if (this.widget && this.content) {
  33. this.visualization = this.content.getFeature('Visualization');
  34. this.filterLabelHelper = new FilterLabelHelper({
  35. dataSource: this.visualization.getDataSource(),
  36. dashboardApi: this.dashboardApi
  37. });
  38. }
  39. this.pageContextAPI = this.controller.model.pageContext.getAPI();
  40. this.extraCssClass = options.extraCssClass || '';
  41. this._ScreenReader = new ScreenReaderUtil();
  42. this.stringResources = this.dashboardApi.getDashboardCoreSvc('.StringResources');
  43. this.flyoutHolder = document.createElement('div');
  44. this.flyoutHolder.classList.add('flyout-holder');
  45. },
  46. render: function render() {
  47. var label = this._getTooltipLabel();
  48. this.el.id = 'timelineHighlight_' + this.id;
  49. this.el.setAttribute('role', 'application');
  50. this.el.className = 'timelineHighlight ' + this.extraCssClass;
  51. this.el.setAttribute('tabindex', '0');
  52. this.el.dataset.selector = 'true';
  53. this.el.dataset.id = this.id;
  54. if (label) {
  55. this.el.setAttribute('aria-label', label);
  56. this.el.setAttribute('title', label);
  57. }
  58. var sHtml = this.dotTemplate({
  59. id: this.id
  60. });
  61. this.$el.html(sHtml);
  62. this._indicatorWidth = this.$el.outerWidth();
  63. this._handleWidth = this.$el.closest('.sliderContent').find('.handle').outerWidth();
  64. // here we assume that timer is inside the widget since it's the persisted value;
  65. this._updateHighlightPosition(this.highlightAct.timer, false);
  66. this._updateHighlightState();
  67. this._registerEvents();
  68. },
  69. remove: function remove() {
  70. this._unregisterEvents();
  71. Indicator.inherited('remove', this, arguments);
  72. },
  73. onSliderChange: function onSliderChange() {
  74. this._updateHighlightPosition(this.highlightAct.timer, false);
  75. },
  76. onScaleChange: function onScaleChange() {
  77. this._updateHighlightPosition(this.highlightAct.timer, false);
  78. },
  79. onPayloadChange: function onPayloadChange() {
  80. this._updateHighlightState();
  81. this._updateTooltipLabel();
  82. },
  83. _updateHighlightState: function _updateHighlightState() {
  84. if (this.highlightAct.get('payload').filter(function (item) {
  85. return item.operator;
  86. }).length) {
  87. this.$el.removeClass('empty');
  88. } else {
  89. this.$el.addClass('empty');
  90. }
  91. },
  92. _updateHighlightPosition: function _updateHighlightPosition(time, isDragging) {
  93. // round to the nearest tick for display
  94. var tick = this.controller.getTickDuration();
  95. time = Math.round(time / tick) * tick;
  96. var position = this.scaleManager.convertTimeToPosition(time);
  97. // the center of the icon on the time
  98. position -= this._indicatorWidth / 2;
  99. // if we are dragging we allow the indicator to be over the handles.
  100. if (!isDragging) {
  101. position = this._limitPositionToInsideHandles(position);
  102. }
  103. this.$el.css('left', position + 'px');
  104. return position;
  105. },
  106. launchHighlightSummaryDialog: function launchHighlightSummaryDialog() {
  107. this.flyoutView = new TimelineHighlightFlyoutController({
  108. dashboardApi: this.dashboardApi,
  109. content: this.content,
  110. visualization: this.visualization,
  111. pageContextAPI: this.pageContextAPI,
  112. services: this.services,
  113. highlightAct: this.highlightAct,
  114. launchView: this,
  115. show: this.launchHighlightSummaryDialog,
  116. launchPoint: this.$el.get(0)
  117. });
  118. this.$el.append(this.flyoutHolder);
  119. this._flyout = React.createElement(StorytellingUI.HighlightSummaryFlyout, {
  120. anchorElement: this.$el.get(0),
  121. closeFlyout: this.closeFlyout.bind(this),
  122. itemActions: this.flyoutView.getItemActions(),
  123. title: this.stringResources.get('timelineHighlightTitle')
  124. });
  125. ReactDOM.render(this._flyout, this.flyoutHolder);
  126. },
  127. getPlacement: function getPlacement() {
  128. return 'top';
  129. },
  130. _registerEvents: function _registerEvents() {
  131. this._dragStartCallback = this._onDragStart.bind(this);
  132. this._dragLeftRightCallback = this._onDragLeftRight.bind(this);
  133. this._dragEndCallback = this._onDragEnd.bind(this);
  134. this._clickCallback = this._onClick.bind(this);
  135. this._onKeyPressCallback = this._onKeyPress.bind(this);
  136. this.$el.hammer({ correct_for_drag_min_distance: false }).on('dragstart', this._dragStartCallback).on('dragleft', this._dragLeftRightCallback).on('dragright', this._dragLeftRightCallback).on('dragend', this._dragEndCallback);
  137. this.$el.find('.svgIcon').on('primaryaction', this._clickCallback);
  138. this.$el.on('keydown', this._onKeyPressCallback);
  139. },
  140. setTime: function setTime(value) {
  141. this._updateHighlightPosition(value, false);
  142. },
  143. _unregisterEvents: function _unregisterEvents() {
  144. if (this._flyout) {
  145. this.closeFlyout();
  146. }
  147. if (this.$el) {
  148. this.$el.off('dragstart', null, this._dragStartCallback).off('dragleft', null, this._dragLeftRightCallback).off('dragright', null, this._dragLeftRightCallback).off('dragend', null, this._dragEndCallback);
  149. this.$el.find('.svgIcon').off('primaryaction', this._clickCallback);
  150. this.$el.off('keydown', this._onKeyPressCallback);
  151. }
  152. },
  153. _onDragLeftRight: function _onDragLeftRight(event) {
  154. if (event.gesture) {
  155. event.gesture.preventDefault();
  156. event.gesture.stopPropagation();
  157. var position = this._positionFromLeft + event.gesture.deltaX;
  158. var time = this.scaleManager.convertPositionToTime(position);
  159. time = this._limitTimerToSlider(time);
  160. this._updateHighlightPosition(time, true);
  161. }
  162. },
  163. _onDragEnd: function _onDragEnd(event) {
  164. if (event.gesture) {
  165. event.gesture.stopPropagation();
  166. this.controller.trigger('timeline:highlightIndicatorDragEnded');
  167. var position = this._positionFromLeft + event.gesture.deltaX;
  168. var time = this.scaleManager.convertPositionToTime(position);
  169. time = this._limitTimerToSlider(time);
  170. this._updateHighlightPosition(time, false);
  171. this.controller.updateTimelineHighlight(this.widgetId, this.id, { timer: time });
  172. }
  173. this._dragEnd = true;
  174. },
  175. _onDragStart: function _onDragStart() {
  176. this.closeFlyout();
  177. this.controller.trigger('timeline:highlightIndicatorDragStarted', this.widgetId);
  178. var position = this.scaleManager.convertTimeToPosition(this.highlightAct.timer);
  179. // the user drags the icon around so we make sure the icon is over the mouse by clamping it to inside the handles
  180. // this means that if the timer value was zero and the user moves 1 pixel it could be something like 200 depending on the scale
  181. // but nothing stops the user from dragging it back to zero.
  182. this._positionFromLeft = this._limitPositionToInsideHandles(position);
  183. },
  184. _updateTooltipLabel: function _updateTooltipLabel() {
  185. var label = this._getTooltipLabel();
  186. if (label !== this.$el.attr('title')) {
  187. this.$el.attr('title', label);
  188. this.$el.attr('aria-label', label);
  189. }
  190. },
  191. _getTooltipLabel: function _getTooltipLabel() {
  192. var _this = this;
  193. var label = this.stringResources.get('timelineHighlightTitle');
  194. if (this.filterLabelHelper) {
  195. var filterItems = this.filterLabelHelper.generateFilterListItems(this.highlightAct.payload);
  196. filterItems.forEach(function (filter) {
  197. label += '\n';
  198. label += _this.stringResources.get('filter_tooltip', {
  199. title: filter.title,
  200. description: filter.description
  201. });
  202. });
  203. }
  204. return label;
  205. },
  206. _onClick: function _onClick(event) {
  207. if (event.type === 'click' && this._dragEnd) {
  208. // Hammer will trigger a ghostclick event after dragend in Firefox
  209. this._dragEnd = false;
  210. return;
  211. }
  212. event.stopPropagation();
  213. event.preventDefault();
  214. if (!this.visAPI) {
  215. // widget has not been loaded. return
  216. var widget = this.widgetHelper.getWidget(this.widgetId);
  217. var content = this.widgetHelper.getContent(this.widgetId);
  218. if (!widget || !content) {
  219. return;
  220. } else {
  221. this.visAPI = widget.getVisApi && widget.getVisApi();
  222. this.visualization = content.getFeature('Visualization');
  223. }
  224. }
  225. this.$el.append(this.flyoutHolder);
  226. var flyout = React.createElement(StorytellingUI.HighlightFlyout, {
  227. widgetId: this.widgetId,
  228. actId: this.id,
  229. controller: this.controller,
  230. anchorElement: this.$el.get(0),
  231. closeFlyout: this.closeFlyout.bind(this),
  232. openSummaryDialog: this.launchHighlightSummaryDialog.bind(this)
  233. });
  234. ReactDOM.render(flyout, this.flyoutHolder);
  235. },
  236. _onKeyPress: function _onKeyPress(event) {
  237. var $target = $(event.currentTarget);
  238. // Set as selected
  239. $target.focus();
  240. // Setup the number of pixels to move based on arrow key pressed
  241. var xMoveBy = 0;
  242. switch (event.keyCode) {
  243. case 13:
  244. case 32:
  245. //Return or Space launches Flyout ODT
  246. this._onClick(event);
  247. return;
  248. case 37:
  249. //Left
  250. xMoveBy = this.scaleManager.convertPositionToTime(-20);
  251. break;
  252. case 39:
  253. //Right
  254. xMoveBy = this.scaleManager.convertPositionToTime(20);
  255. break;
  256. case 38:
  257. case 40:
  258. event.stopPropagation();
  259. event.preventDefault();
  260. return;
  261. default:
  262. return;
  263. }
  264. event.stopPropagation();
  265. event.preventDefault();
  266. //Moving from the current position.
  267. var time = this._limitTimerToSlider(this.highlightAct.timer + xMoveBy);
  268. this._updateHighlightPosition(time, false);
  269. this.controller.updateTimelineHighlight(this.widgetId, this.id, { timer: time });
  270. var messageOptions = {
  271. id: this.id,
  272. time: time
  273. };
  274. var sMessage = this.stringResources.get('timeline_highlight_moved_to', messageOptions);
  275. this._ScreenReader.callOut(sMessage);
  276. },
  277. _limitTimerToSlider: function _limitTimerToSlider(timerValue) {
  278. timerValue = Math.max(timerValue, this.episodeModel.getEntranceAct().timer + 1);
  279. timerValue = Math.min(timerValue, this.episodeModel.getExitAct().timer - 1);
  280. return timerValue;
  281. },
  282. _limitPositionToInsideHandles: function _limitPositionToInsideHandles(position) {
  283. var entranceTime = this.episodeModel.getEntranceAct().timer;
  284. var min = this.scaleManager.convertTimeToPosition(entranceTime);
  285. min += this._handleWidth;
  286. var exitTime = this.episodeModel.getExitAct().timer;
  287. var max = this.scaleManager.convertTimeToPosition(exitTime);
  288. max -= this._handleWidth + this._indicatorWidth;
  289. position = Math.min(position, max);
  290. position = Math.max(position, min);
  291. // if there is not enough space between the handles we split the difference.
  292. var contentWidth = this.scaleManager.convertTimeToPosition(exitTime - entranceTime - this._handleWidth * 2);
  293. if (contentWidth < this._indicatorWidth) {
  294. position -= (this._indicatorWidth - contentWidth) / 2;
  295. }
  296. return position;
  297. },
  298. closeFlyout: function closeFlyout() {
  299. ReactDOM.unmountComponentAtNode(this.flyoutHolder);
  300. this.$el.find('.flyout-holder').remove();
  301. }
  302. });
  303. return Indicator;
  304. });
  305. //# sourceMappingURL=TimelineHighlightIndicatorView.js.map