VisView.js 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2014, 2020
  5. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  6. *
  7. * @module dashboard/render/VisView
  8. * @see VisView
  9. */
  10. define(['jquery', 'underscore', 'doT', '../../lib/@waca/core-client/js/core-client/ui/core/View', './filter/FilterIndicator', './info/InfoIndicator', '../../widgets/livewidget/nls/StringResources', '../../lib/@waca/core-client/js/core-client/utils/Utils', './refreshtimer/RefreshTimerIndicator', 'text!./templates/EmptyVisualization.template', './forecastIndicator/ForecastIndicator', './VisTabs', '../../lib/@waca/core-client/js/core-client/utils/BrowserUtils', '../../widgets/livewidget/util/VisUtil', '../../lib/@waca/dashboard-common/dist/utils/MemUtil'], function ($, _, dot, View, FilterIndicator, InfoIndicator, StringResources, Utils, RefreshTimerIndicator, EmptyVisualizationTemplate, ForecastIndicator, VisTabs, BrowserUtils, VisUtil, MemUtil) {
  11. //Track sub-views created to help with cleanup
  12. var VisViewSubViews = ['filterIndicator', 'refreshTimerIndicator', 'conditionalViewIndicator', 'infoIndicator'];
  13. /**
  14. * VisView is the base class of all Visualization views
  15. * such as RaveView, GridView etc.
  16. */
  17. var VisView = View.extend({
  18. //The animation speed for all operations if no animation speed is set in properties.
  19. DEFAULT_ANIMATION_SPEED: 900,
  20. //The animation type (if none is set, default is currently to fadein).
  21. ANIMATION_TYPES: {
  22. NONE: 'none',
  23. DEFAULT: 'default',
  24. FADEIN: 'fadein',
  25. TRANSITION: 'transition',
  26. GROW: 'grow',
  27. REVEAL: 'reveal',
  28. BIGDATA: 'bigdata'
  29. },
  30. init: function init(options) {
  31. VisView.inherited('init', this, arguments);
  32. _.extend(this, arguments[0]);
  33. this.noRotate = true;
  34. this.isMaximizable = true;
  35. this.logger = options.logger;
  36. this.ownerWidget = options.ownerWidget;
  37. this.visualization = this.ownerWidget.getVisualizationApi();
  38. this.transactionApi = this.dashboardApi.getFeature('Transaction');
  39. this.internalVisDefinitions = this.dashboardApi.getFeature('VisDefinitions.internal');
  40. this.content = options.ownerWidget.content;
  41. this._processTemplateString();
  42. if (!options.proxiedView && !this._viewManagesOwnQuery()) {
  43. this._initIndicators();
  44. }
  45. this.visEl = this.el.firstChild ? this.el.firstChild : this.el;
  46. // create the visualization tabs (if supported)
  47. this.createTabs();
  48. //When set by an event handler (aka filter), this value controls the animation speed
  49. //for views that otherwise don't animate (but do support it).
  50. //If the definition already has animation, it will NOT override its value.
  51. this.overrideDefaultAnimationSpeed = -1;
  52. //This setting controls Non-VIPR visualizations allowing them to fade-in/fade-out during rendering.
  53. //VIPR visualizations use effects which are separate from this setting.
  54. //By default, Non-VIPR animation is off: (new for Endor) as it causes needless flashing and makes visualizaions appear slower.
  55. this.animationType = this.ANIMATION_TYPES.NONE;
  56. this.animationSpeed = this.DEFAULT_ANIMATION_SPEED;
  57. //The 'renderingNewData' state (maintained by the rendersequnce) is relevant in the context of the view's render() method.
  58. //It means that, for a particular view.render() call, new data is being rendered.
  59. this._renderingNewData = false;
  60. if (!options.proxiedView && !this._viewManagesOwnQuery()) {
  61. this.createView();
  62. }
  63. if (!options.proxiedView) {
  64. this._addPropertyHandler();
  65. }
  66. },
  67. createTabs: function createTabs() {
  68. var options = {};
  69. // reuse the existing DIV if already exists
  70. if (this.ownerWidget && this.ownerWidget.$el) {
  71. var $tabs = this.ownerWidget.$el.find('.visTabsView');
  72. options.el = $tabs.length ? $tabs[0] : null;
  73. }
  74. this._tabs = new VisTabs(options);
  75. // keep it hidden until render
  76. this._tabs.hide();
  77. },
  78. placeAt: function placeAt(element) {
  79. this.$el.prependTo(element); // insert view as the first element of the content container
  80. //if tab view exists, insert tabs as the first element
  81. if (this._tabs) {
  82. this._tabs.placeAt(element);
  83. }
  84. },
  85. setInExpandedMode: function setInExpandedMode() {
  86. var conditionalViewIndicator = this.content.getFeature('ConditionalViewIndicator');
  87. if (conditionalViewIndicator) {
  88. conditionalViewIndicator.setInExpandedMode();
  89. }
  90. },
  91. onRestore: function onRestore() {
  92. var conditionalViewIndicator = this.content.getFeature('ConditionalViewIndicator');
  93. if (conditionalViewIndicator) {
  94. conditionalViewIndicator.onRestore();
  95. }
  96. },
  97. /**
  98. * @returns if this specific view supports annotations. Returns false by default, expected to be overriden.
  99. */
  100. _doesViewSupportSmartAnnotations: function _doesViewSupportSmartAnnotations() {
  101. return false;
  102. },
  103. // to be implemented by chilren class
  104. doesVisPropertyMatchExpected: function doesVisPropertyMatchExpected() {
  105. return false;
  106. },
  107. // to be implemented by chilren class
  108. canApplyAutoBin: function canApplyAutoBin() {
  109. return false;
  110. },
  111. // to be implemented by chilren class
  112. getBinningConfig: function getBinningConfig() {
  113. return undefined;
  114. },
  115. /*
  116. * Reads the filter indicator config spec, and instantiates a filter indicator if applicable.
  117. */
  118. _initFilterIndicator: function _initFilterIndicator() {
  119. var filterSpec = this.getFilterIndicatorSpec();
  120. if (filterSpec && (filterSpec.localFilters || filterSpec.globalFilters || filterSpec.localTopBottoms || filterSpec.drillState)) {
  121. this.filterIndicator = new FilterIndicator(this.visModel, this.visualization, this.transactionApi, this.getController(), filterSpec, this.logger);
  122. this.ownerWidget.addIcon(this.filterIndicator.$filter, 'filterIcon');
  123. }
  124. },
  125. _initIndicators: function _initIndicators() {
  126. var _this = this;
  127. var visIndicators = this.content.getFeature('VisIndicators');
  128. if (visIndicators && this.ownerWidget && this.ownerWidget.$el) {
  129. this._initRefreshIndicator();
  130. this._initFilterIndicator();
  131. this._initInfoIndicator();
  132. this._initForecastingIndicator();
  133. var indicators = visIndicators.getIndicatorList();
  134. _.each(indicators, function (indicator) {
  135. _this.ownerWidget.addIcon(indicator.icon, indicator.name);
  136. });
  137. }
  138. },
  139. _initForecastingIndicator: function _initForecastingIndicator() {
  140. this.forecastIndicator = new ForecastIndicator({
  141. visModel: this.visModel,
  142. ownerWidget: this.ownerWidget,
  143. logger: this.logger,
  144. supportsForecasts: true,
  145. ownerView: this,
  146. visAPI: this.ownerWidget.visAPI,
  147. content: this.content
  148. });
  149. this.ownerWidget.addIcon(this.forecastIndicator.getIndicator(), 'forecastIcon');
  150. },
  151. _initInfoIndicator: function _initInfoIndicator() {
  152. this.infoIndicator = new InfoIndicator({
  153. visModel: this.visModel,
  154. ownerWidget: this.ownerWidget
  155. });
  156. this.ownerWidget.addIcon(this.infoIndicator.$el, 'infoIconDiv');
  157. },
  158. _initRefreshIndicator: function _initRefreshIndicator() {
  159. this.refreshTimerIndicator = new RefreshTimerIndicator(this.ownerWidget);
  160. this.ownerWidget.addIcon(this.refreshTimerIndicator.$icon, 'timerIcon');
  161. },
  162. /*
  163. * Indicates whether to render filter flyouts
  164. */
  165. getFilterIndicatorSpec: function getFilterIndicatorSpec() {
  166. return {
  167. localFilters: true,
  168. globalFilters: true,
  169. localTopbottoms: true,
  170. drillState: true
  171. };
  172. },
  173. getController: function getController() {
  174. return this.content && this.content.getFeature('InteractivityController.deprecated');
  175. },
  176. getSelector: function getSelector() {
  177. return this.content.getFeature('DataPointSelections.deprecated');
  178. },
  179. /*
  180. * Indicates whether to render topbottom flyouts. Unlike filter, topbottom only applys to the current viz there is no global
  181. */
  182. /**
  183. * Render Sequence - override this method when needed by a specific view type.
  184. * @returns promise - By default, views don't have controls....return a resolved promise with an empty object.
  185. */
  186. whenVisControlReady: function whenVisControlReady() {
  187. return this._noOpRenderStep('base class whenVisControlReady called!');
  188. },
  189. /**
  190. * Render Sequence - override this method when needed by a specific view type.
  191. * @returns promise - By default, views don't have visSpecs....return a resolved promise with an empty object.
  192. */
  193. whenVisSpecReady: function whenVisSpecReady() {
  194. return this._noOpRenderStep('base class whenVisSpecReady called!');
  195. },
  196. /**
  197. * @returns {boolean} true if this specific view supports annotations. Returns
  198. * false by default. Expected to be overriden
  199. */
  200. supportsAnnotations: function supportsAnnotations() {
  201. return false;
  202. },
  203. /**
  204. * @returns {boolean} true if this specific view supports annotations. Returns
  205. * false by default. Expected to be overriden
  206. */
  207. supportsForecasts: function supportsForecasts() {
  208. return false;
  209. },
  210. /**
  211. * Called before data is requested. Use this to clean up infoIndicator, etc.
  212. */
  213. preDataReady: function preDataReady() {
  214. // Reset any warnings/errors in the info indicator as we are starting fresh
  215. if (this.infoIndicator) {
  216. this.infoIndicator.clearMessagesWithIds(['visualization_notifications', 'moreDataIndicator', 'autobinSuggestion', 'data_notifications']);
  217. }
  218. },
  219. _hasMappedSlots: function _hasMappedSlots() {
  220. return this.visualization.getSlots().getMappedSlotList().length !== 0;
  221. },
  222. /**
  223. * Render Sequence - override this method when needed by a specific view type.
  224. * @returns promise - By default, make a query for the data and do not index the results (ie: RaveView is an exception).
  225. */
  226. whenDataReady: function whenDataReady(renderContext) {
  227. var _this2 = this;
  228. var result = void 0;
  229. // A resize event is forced when the layout base view is initialized. This may cause a request
  230. // to render the visualization before the visControl is created. Guard against this.
  231. if (this._hasMappedSlots() && this.canExecuteQuery() && !this.hasUnavailableMetadataColumns() && !this.hasMissingFilters()) {
  232. renderContext.isDevInstall = this.ownerWidget.getDashboardApi().isDevInstall;
  233. result = this._executeQueries(renderContext).then(function (retData) {
  234. _this2.dashboardApi.triggerDashboardEvent('synchronize:pageContextFilters', { synchDataFilterEntries: retData.synchDataFilterEntries });
  235. if (_this2.filterIndicator) {
  236. _this2.filterIndicator.setSynchronizeDataFilters(retData.synchDataFilterEntries);
  237. _this2.filterIndicator.update();
  238. }
  239. _this2._updateInfoIndicator(retData, renderContext);
  240. return {
  241. data: retData,
  242. sameQueryData: _this2.isSameQueryData(renderContext, retData)
  243. };
  244. });
  245. } else {
  246. result = Promise.resolve({
  247. data: {}
  248. });
  249. }
  250. return result;
  251. },
  252. _updateInfoIndicator: function _updateInfoIndicator(queryResults, renderContext) {
  253. var hasMoreData = void 0;
  254. var queryThreshold = void 0;
  255. var warnings = void 0;
  256. if (renderContext.useAPI) {
  257. var queryId = _.find(queryResults.getQueryResultIdList(), function (id) {
  258. return queryResults.getResult(id).hasMoreData();
  259. });
  260. hasMoreData = queryId !== undefined;
  261. queryThreshold = hasMoreData && queryResults.getResult(queryId).getRowCount();
  262. warnings = queryResults.getWarningList();
  263. } else {
  264. hasMoreData = queryResults.hasMoreData;
  265. queryThreshold = queryResults.queryThreshold;
  266. }
  267. if (this.infoIndicator) {
  268. var infoItems = [];
  269. // @todo: utilize the QueryResultsAPI.getWarningList
  270. if (hasMoreData) {
  271. // add data clipping
  272. if (queryThreshold) {
  273. infoItems.push({
  274. id: 'moreDataIndicator',
  275. label: StringResources.get('moreDataIndicator', { threshold: queryThreshold })
  276. });
  277. }
  278. // check auto-binning: 1) is it supported and 2) not already binned
  279. if (this.canApplyAutoBin() && !this.hasBinnedDataItems()) {
  280. infoItems.push({
  281. id: 'autobinSuggestion',
  282. label: StringResources.get('autobinSuggestion'),
  283. isSubMessage: true
  284. });
  285. }
  286. }
  287. if (warnings && warnings.length) {
  288. warnings.forEach(function (warning) {
  289. infoItems.push({
  290. id: warning.resourceId,
  291. label: StringResources.get(warning.resourceId, warning.params),
  292. isSubMessage: !!warning.isSubMessage
  293. });
  294. });
  295. }
  296. this.infoIndicator.addInfo([{
  297. id: 'data_notifications',
  298. label: StringResources.get('dataNotifications'),
  299. items: infoItems
  300. }]);
  301. }
  302. },
  303. _executeQueries: function _executeQueries(renderContext) {
  304. if (renderContext.useAPI) {
  305. var queryExecution = this.content.getFeature('DataQueryExecution');
  306. if (renderContext.extraInfo && renderContext.extraInfo.dataQueryParams) {
  307. queryExecution.addRequestOptions(renderContext.extraInfo.dataQueryParams);
  308. }
  309. return queryExecution.executeQueries();
  310. } else {
  311. return this.visModel.whenQueryResultsReady(renderContext);
  312. }
  313. },
  314. /**
  315. * Render Sequence - override this method when needed by a specific view type.
  316. * @returns {Promise}
  317. */
  318. whenSetDataReady: function whenSetDataReady() /* renderContext */{
  319. return Promise.resolve(true);
  320. },
  321. isSameQueryData: function isSameQueryData() {
  322. var renderContext = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  323. var queryResult = arguments[1];
  324. // TODO clean logic when QueryAPI is live.
  325. if (renderContext.sameQueryData === undefined) {
  326. var currTag = queryResult && queryResult.getCacheValidateTag && queryResult.getCacheValidateTag();
  327. renderContext.sameQueryData = this._previousTag && this._previousTag === currTag;
  328. this._previousTag = currTag;
  329. }
  330. return !!(renderContext.sameQueryData || renderContext.extraInfo && renderContext.extraInfo.annotationRequest);
  331. },
  332. /* For Subclass to implement */
  333. updateInfoIndicator: function updateInfoIndicator(data) {
  334. if (this.infoIndicator) {
  335. this.infoIndicator.update(data);
  336. }
  337. },
  338. /**
  339. * The 'renderingNewData' state (maintained by the renderSequence) is relevant in the context of the view's render() method.
  340. * It means that, for a particular view.render() call, new data is being rendered.
  341. * (for example, typically, render following a resize would not be renderingNewData BUT,
  342. * If a resize occurs quickly after a render, 2 RenderSequence render() calls could result in one view:render() call
  343. * (effecively the 2 renders are "bundled into 1" from the perspective of the view so we are renderingNewData)
  344. * @param {boolean} flag - The renderingNewData value is maintained by the render sequence as follows:
  345. * true: when the data task has executed a query and has new data.
  346. * false: when the render task has completed a render
  347. */
  348. setRenderingNewData: function setRenderingNewData(flag) {
  349. this._renderingNewData = flag;
  350. },
  351. /**
  352. * @returns the state of the renderingNewData flag. This call makes sense in the context of a particular view.render().
  353. */
  354. getRenderingNewData: function getRenderingNewData() {
  355. return this._renderingNewData;
  356. },
  357. /**
  358. * @returns true if all of the required slots are mapped and this visualization can be rendered.
  359. */
  360. isMappingComplete: function isMappingComplete() {
  361. return this.visualization.getSlots().isMappingComplete();
  362. },
  363. /**
  364. * Determine whether the query can be executed.
  365. * Generally the query shall execute when all required mapping is complete.
  366. * However some applications may require to execute the query regardless of the slot mapping state.
  367. * The config should only affect the behaviour of the query and should have no impact on the actual render
  368. * of the visualization. The render should continue to render only when isMappingComplete is true.
  369. */
  370. canExecuteQuery: function canExecuteQuery() {
  371. return this.dashboardApi.getConfiguration('forceQuery') === true || this.isMappingComplete();
  372. },
  373. isRenderWithoutCompletingMapping: function isRenderWithoutCompletingMapping() {
  374. var definition = this.visualization.getDefinition();
  375. return definition.getProperty('renderWithoutCompletingMapping') === true;
  376. },
  377. hasUnavailableMetadataColumns: function hasUnavailableMetadataColumns() {
  378. return this.visualization.hasUnavailableMetadataColumns();
  379. },
  380. /**
  381. * Render Sequence - override this method when needed by a specific view type.
  382. * @returns promise - By default, views don't have visSpecs....return a resolved promise with an empty object.
  383. */
  384. whenHighlighterReady: function whenHighlighterReady() {
  385. return this._noOpRenderStep('base class whenHighlighterReady called!');
  386. },
  387. /**
  388. * Creates an object consumable by our info indicator
  389. * @param {object} id id of our info, needs a matching entry in our nls entries
  390. * @param {Array} items
  391. * @param {String} item.id
  392. * @param {String} item.caption
  393. */
  394. _getInfoIndicatorEntries: function _getInfoIndicatorEntries(id, items) {
  395. return [{
  396. id: id,
  397. label: StringResources.get(id),
  398. items: _.map(items, function (item) {
  399. return {
  400. id: item.id,
  401. label: item.caption
  402. };
  403. })
  404. }];
  405. },
  406. _setInfoIndicatorWarningsAndErrors: function _setInfoIndicatorWarningsAndErrors(data) {
  407. if (this.infoIndicator && data) {
  408. var errorId = 'smart_errors';
  409. var warningId = 'smart_warnings';
  410. var warnings = data.getWarnings();
  411. var errors = data.getErrors();
  412. this.infoIndicator.addInfo(this._getInfoIndicatorEntries(warningId, warnings));
  413. this.infoIndicator.addInfo(this._getInfoIndicatorEntries(errorId, errors));
  414. }
  415. },
  416. clearInfoIndicator: function clearInfoIndicator() {
  417. // Reset any warnings/errors in the info indicator as we are starting fresh
  418. if (this.infoIndicator) {
  419. //if our mapping isn't complete, just clear out the info indicator.
  420. if (!this.isMappingComplete()) {
  421. this.infoIndicator.reset();
  422. } else {
  423. for (var _len = arguments.length, msgId = Array(_len), _key = 0; _key < _len; _key++) {
  424. msgId[_key] = arguments[_key];
  425. }
  426. this.infoIndicator.clearMessagesWithIds(msgId);
  427. }
  428. }
  429. },
  430. clearAnnotationErrorsAndWarnings: function clearAnnotationErrorsAndWarnings() {
  431. this.clearInfoIndicator('smart_errors', 'smart_warnings', 'smart_exec_support_warnings', 'annotation_service_error');
  432. },
  433. /**
  434. * Render Sequence - override this method when needed by a specific view type.
  435. * @returns promise - By default, views don't have visSpecs....return a resolved promise with an empty object.
  436. */
  437. whenAnnotatedResultsReady: function whenAnnotatedResultsReady(renderContext) {
  438. var _this3 = this;
  439. if (this.isMappingComplete() && !this.hasUnavailableMetadataColumns() && (this._checkAllSupportsAnnotations() || this._checkAllSupportsForecasts())) {
  440. return this.visModel.whenAnnotatedResultsReady(renderContext).then(function (data) {
  441. _this3._setInfoIndicatorWarningsAndErrors(data);
  442. return data;
  443. });
  444. } else {
  445. return Promise.resolve();
  446. }
  447. },
  448. addUnavailableAnnotationServiceErrorMessageToIndicator: function addUnavailableAnnotationServiceErrorMessageToIndicator() {
  449. if (this.infoIndicator) {
  450. this.infoIndicator.addInfo([{
  451. id: 'annotation_service_error',
  452. label: StringResources.get('smart_errors'),
  453. items: [{
  454. id: 'suggestions',
  455. label: StringResources.get('smart_annotation_insight_unavailable')
  456. }]
  457. }]);
  458. }
  459. },
  460. /**
  461. * Render Sequence - override this method when needed by a specific view type.
  462. * @returns {object} promise
  463. */
  464. whenPredictSuggestionsReady: function whenPredictSuggestionsReady(renderContext) {
  465. var _this4 = this;
  466. this.clearInfoIndicator('annotation_service_error', 'suggestions');
  467. // Clear the messages so that we aren't showing stale info
  468. this.visModel.clearInsightsIndicatorMessages();
  469. if (this.content) {
  470. this.content.getFeature('Forecast').clearForecastIndicatorMessages();
  471. }
  472. var supportsAnnotations = this._checkAllSupportsAnnotations();
  473. if (!supportsAnnotations) {
  474. this.visModel.resetAnnotations();
  475. }
  476. if (this._hasMappedSlots() && this.isMappingComplete()) {
  477. return this.visModel.whenPredictIsReady(renderContext, supportsAnnotations, this._checkAllSupportsForecasts()).catch(function (error) {
  478. _this4.addUnavailableAnnotationServiceErrorMessageToIndicator();
  479. _this4.logger.error('An error occurred while waiting for the annotation suggestions to be ready', error, _this4.visModel);
  480. });
  481. }
  482. return Promise.resolve();
  483. },
  484. _checkAllSupportsAnnotations: function _checkAllSupportsAnnotations() {
  485. return this.supportsAnnotations() && this._viewSupportsAnnotations();
  486. },
  487. //We enable annotations for preview launched from Driver analysis Viz key drivers,
  488. //Except that, for other previews, we disable annotations.
  489. _viewSupportsAnnotations: function _viewSupportsAnnotations() {
  490. if (this.ownerWidget.isPreview) {
  491. return this.ownerWidget.isPredictPreview === true;
  492. }
  493. return true;
  494. },
  495. _checkAllSupportsForecasts: function _checkAllSupportsForecasts() {
  496. return this.supportsForecasts() && this._viewSupportsForecasts();
  497. },
  498. _viewManagesOwnQuery: function _viewManagesOwnQuery() {
  499. if (this.ownerWidget.managesOwnQueries) {
  500. return this.ownerWidget.managesOwnQueries === true;
  501. }
  502. return false;
  503. },
  504. //We enable forecasts for preview launched from Driver analysis Viz key drivers,
  505. //Except that, for other previews, we disable annotations.
  506. _viewSupportsForecasts: function _viewSupportsForecasts() {
  507. if (this.ownerWidget.isPreview) {
  508. return this.ownerWidget.isPredictPreview === true;
  509. }
  510. return true;
  511. },
  512. /**
  513. * Render Sequence:
  514. * Each step in the render sequence returns a promise.
  515. * For any view, or any step, this method can be used to return a resolved promise with an empty object
  516. */
  517. _noOpRenderStep: function _noOpRenderStep() {
  518. //By default, VisViews have no underlying VisControl...return an empty, non-null object.
  519. return Promise.resolve({});
  520. },
  521. _addPropertyHandler: function _addPropertyHandler() {
  522. this.ownerWidget.model.on('change:properties', this.onChangeProperties, this);
  523. this.ownerWidget.model.on('change:fillColor', this.onChangeProperties, this);
  524. },
  525. /**
  526. * Set up the View and standard event handlers on the VisModel.
  527. */
  528. createView: function createView() {
  529. if (!this.visModel) {
  530. throw 'Invalid VisModel reference';
  531. }
  532. //Subscribe to model events
  533. this.visModelEvents = {
  534. 'change:annotations': this.onChangeAnnotations,
  535. 'change:pagecontext': this.onChangePageContext, //pageContext changes should be processed as filters.
  536. 'change:refreshTimer': this.toggleRefreshTimerIndicator
  537. };
  538. this.ownerWidget.model.on('change:searchFilters', this.onChangeLocalFilter, this);
  539. this.ownerWidget.model.on('change:localFilters', this.onChangeLocalFilter, this);
  540. this.ownerWidget.model.on('change:conditions', this.onChangeConditions, this);
  541. this.ownerWidget.model.on('change:possibleKeyDrivers', this.onChangePossibleKeyDrivers, this);
  542. for (var key in this.visModelEvents) {
  543. if (this.visModelEvents.hasOwnProperty(key)) {
  544. this.visModel.on(key, this.visModelEvents[key], this);
  545. }
  546. }
  547. if (this.filterIndicator) {
  548. this.filterIndicator.initEvents();
  549. }
  550. if (this.refreshTimerIndicator) {
  551. this.refreshTimerIndicator.initEvents();
  552. }
  553. if (this.forecastIndicator) {
  554. this.forecastIndicator.initEvents(this.supportsForecasts());
  555. }
  556. var content = this.ownerWidget.content;
  557. content && content.on('all', this.onAllContentChanges, this);
  558. var visDefinitions = this.dashboardApi.getFeature('VisDefinitions');
  559. visDefinitions.on('refresh:definition', this.onRefreshVisDefinition, this);
  560. visDefinitions.on('refresh:all', this.onRefreshAllVisDefinition, this);
  561. var colorsService = this.dashboardApi.getFeature('Colors');
  562. colorsService.on('theme:changed', this.onChangeProperties, this);
  563. },
  564. /**
  565. * Event handler when possible key drivers are changed inside vis model
  566. */
  567. onChangePossibleKeyDrivers: function onChangePossibleKeyDrivers() {
  568. var event = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  569. if (this._isViewSlotsEmpty()) {
  570. return this.ownerWidget._rebuildVis(event);
  571. }
  572. var reRenderOptions = {
  573. refresh: {
  574. predictSuggestions: true,
  575. keyDrivers: true,
  576. data: true
  577. }
  578. };
  579. if (this.filterIndicator) {
  580. this.filterIndicator.update();
  581. }
  582. this.appendExtraInfoToRenderOptions(reRenderOptions, event);
  583. if (this.ownerWidget.isVisible()) {
  584. this._reRender(reRenderOptions);
  585. } else {
  586. this.ownerWidget.setReRenderOnShow(reRenderOptions);
  587. }
  588. },
  589. onRefreshAllVisDefinition: function onRefreshAllVisDefinition() {
  590. var visDefinition = this.visualization.getDefinition();
  591. if (visDefinition.getState().getError() || this.content.getFeature('state').getError()) {
  592. this._reRender({
  593. refreshAll: true
  594. });
  595. }
  596. },
  597. onRefreshVisDefinition: function onRefreshVisDefinition(event) {
  598. var visDefinition = this.visualization.getDefinition();
  599. if (visDefinition && event && event.info && visDefinition.getId() === event.info.id) {
  600. this._reRender({
  601. refreshAll: true
  602. });
  603. }
  604. },
  605. onAllContentChanges: function onAllContentChanges(e) {
  606. if (e && e.info && e.info.refresh) {
  607. var renderOptions = {
  608. refresh: _.extend({}, e.info.refresh),
  609. extraInfo: {
  610. payloadData: {
  611. transactionToken: e.transactionToken
  612. }
  613. }
  614. };
  615. if (renderOptions.refresh.all) {
  616. // TODO: refreshAll should be changed into 'refresh.all' to be consistent with the refresh syntax
  617. renderOptions.refreshAll = renderOptions.refresh.all;
  618. }
  619. // Type change
  620. // highlighting is included because this event may have
  621. // been triggered from a failure handler.
  622. // ensure the remaining rendering sequence is covered
  623. // var reRenderOptions = {
  624. // refresh: {
  625. // visSpec: true,
  626. // data: true,
  627. // highlighter: true,
  628. // predictSuggestions: true
  629. // }
  630. // };
  631. // //If currently rendering icon view, ensure the proper control is loaded.
  632. // reRenderOptions.refresh.visControl = true;
  633. // this._reRender(this.appendExtraInfoToRenderOptions(reRenderOptions, event));
  634. // this.updateConditionalViewIndicator();
  635. // Todo: once we removed the logic for refresh properties pane upon re-render, this should be removed too
  636. // UndoRedo action will update the model but the old properties UI doesn't have state management, so we need to do a refresh for this scenario
  637. var isUndoRedo = e.context && e.context.undoRedo && e.info.featureName !== 'ConditionalFormatting';
  638. renderOptions.refresh.propertiesPane = this._refreshPropertiesPane(renderOptions, isUndoRedo);
  639. this._reRender(renderOptions);
  640. }
  641. },
  642. _refreshPropertiesPane: function _refreshPropertiesPane(renderOptions, isUndoRedo) {
  643. return !this._isLocalViz() || renderOptions.refresh.propertiesPane || !this._isNewConditionalFormattingEnabled() || !!isUndoRedo;
  644. },
  645. _isLocalViz: function _isLocalViz() {
  646. var localVizType = ['KPI', 'Singleton', 'Hierarchy', 'Crosstab', 'List', 'DataPlayer'];
  647. var visType = this.content.getFeature('Visualization').getType();
  648. return _.contains(localVizType, visType);
  649. },
  650. _isNewConditionalFormattingEnabled: function _isNewConditionalFormattingEnabled() {
  651. var cf_kpi = !this.dashboardApi.getGlassCoreSvc('.FeatureChecker').checkValue('dashboard', 'condFormat', 'disabled');
  652. var cf_xtab = !this.dashboardApi.getGlassCoreSvc('.FeatureChecker').checkValue('dashboard', 'xtabcondFormat', 'disabled');
  653. return cf_kpi || cf_xtab;
  654. },
  655. updateSubViews: function updateSubViews() {
  656. this.updateConditionalViewIndicator();
  657. },
  658. /**
  659. * If this view has a template string, make its content a child of the root "el".
  660. * If the template includes "data-attach-point" attributes, augment the view class
  661. * with them.
  662. * eg:
  663. * <root>
  664. * <div class="x" data-attach-point='sliderNode'>
  665. * <div class="y" data-attach-point='playerButtonNode'>
  666. * </root>
  667. *
  668. * would extend the VisView with members sliderNode=div.x, playerButtonNode=div.y
  669. */
  670. _processTemplateString: function _processTemplateString() {
  671. if (this.templateString) {
  672. //Populate the view with the template if one has been defined.
  673. var outTemplate = this.dotTemplate(this.templateString);
  674. if (!outTemplate) {
  675. outTemplate = this.templateString;
  676. }
  677. this.$el.html(outTemplate);
  678. var dataAttachPoints = this.el.querySelectorAll('div[data-attach-point]');
  679. var oAttachPoints = {};
  680. _.each(dataAttachPoints, function (dataAttachPoint) {
  681. oAttachPoints[dataAttachPoint.getAttribute('data-attach-point')] = dataAttachPoint;
  682. });
  683. _.extend(this, oAttachPoints);
  684. }
  685. },
  686. /**
  687. * Resize the el for this view to the specified bounds
  688. * @param bounds in form left, top, width, height.
  689. */
  690. resize: function resize(bounds) {
  691. if (bounds) {
  692. // capture widget height, if zero or undefined then get parent's height
  693. var height = bounds.height || this.$el.parent().height();
  694. // if we have tabs, we must compensate for the space it takes within the widget if the mapping is complete
  695. if (this._tabs && this._tabs.getTabsCount() > 0 && this.ownerWidget.allowShowTabs && this.ownerWidget.allowShowTabs() && this.isMappingComplete()) {
  696. height = height - this._tabs.$el.height();
  697. }
  698. var fSetBound = function fSetBound(style, styleAttr, val) {
  699. // Setting style may be expensive, do it only when necessary.
  700. if (val !== undefined && val !== parseInt(style[styleAttr], 10)) {
  701. style[styleAttr] = val + 'px';
  702. }
  703. };
  704. var style = this.el.style;
  705. fSetBound(style, 'left', bounds.left);
  706. fSetBound(style, 'top', bounds.top);
  707. fSetBound(style, 'width', bounds.width);
  708. fSetBound(style, 'height', height);
  709. }
  710. },
  711. /**
  712. * Remove this view...cleanup any event handlers set up
  713. * and perform base class cleanup.
  714. */
  715. // @override
  716. remove: function remove() {
  717. var _this5 = this;
  718. var finalRemove = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
  719. this.removeIconView();
  720. VisView.inherited('remove', this, arguments);
  721. // todo livewidget_cleanup -- it is weird that we pass a finalRemove
  722. // It seems to be used to delayed the destruction of a vipr widget
  723. // but we could handle this in a better way
  724. VisViewSubViews.forEach(function (view) {
  725. _this5[view] && _this5[view].remove();
  726. _this5[view] = null;
  727. });
  728. // external events
  729. for (var key in this.visModelEvents) {
  730. if (this.visModelEvents.hasOwnProperty(key)) {
  731. this.visModel.off(key, this.visModelEvents[key], this);
  732. }
  733. }
  734. this.ownerWidget.model.off('change:searchFilters', this.onChangeLocalFilter, this);
  735. this.ownerWidget.model.off('change:localFilters', this.onChangeLocalFilter, this);
  736. this.ownerWidget.model.off('change:properties', this.onChangeProperties, this);
  737. this.ownerWidget.model.off('change:fillColor', this.onChangeProperties, this);
  738. this.ownerWidget.model.off('change:conditions', this.onChangeConditions, this);
  739. this.ownerWidget.model.off('change:possibleKeyDrivers', this.onChangePossibleKeyDrivers, this);
  740. if (this._tabs) {
  741. this._tabs.remove();
  742. this._tabs = null;
  743. }
  744. var content = this.ownerWidget.content;
  745. content && content.off('all', this.onAllContentChanges, this);
  746. var visDefinitions = this.dashboardApi.getFeature('VisDefinitions');
  747. visDefinitions.off('refresh:definition', this.onRefreshVisDefinition, this);
  748. visDefinitions.off('refresh:all', this.onRefreshAllVisDefinition, this);
  749. var colorsService = this.dashboardApi.getFeature('Colors');
  750. colorsService.off('theme:changed', this.onChangeProperties, this);
  751. if (finalRemove) {
  752. MemUtil.destroy(this);
  753. }
  754. },
  755. onChangePageContext: function onChangePageContext(event) {
  756. var _this6 = this;
  757. return this.onChangeFilter(event).catch(function (e) {
  758. var LOG_LEVEL = e && e.message === 'dwErrorStaleRequest' ? 'debug' : 'error';
  759. _this6.logger[LOG_LEVEL](e);
  760. });
  761. },
  762. _rebuildVis: function _rebuildVis(event, options) {
  763. this._rebuildPending = true;
  764. var transactionToken = options && options.extraInfo && options.extraInfo.payloadData && options.extraInfo.payloadData.transactionToken;
  765. return this.transactionApi.registerTransactionHandler(transactionToken || {}, this.ownerWidget.id + '_rebuildvis', function () {
  766. var _this7 = this;
  767. this.ownerWidget._rebuildVis(event).then(function () {
  768. _this7._rebuildPending = false;
  769. });
  770. }.bind(this), options);
  771. },
  772. _getRenderTransaction: function _getRenderTransaction(renderOptions) {
  773. var transaction = renderOptions && renderOptions.extraInfo && renderOptions.extraInfo.payloadData && renderOptions.extraInfo.payloadData.transactionToken;
  774. return !transaction || transaction.transactionId ? transaction : null;
  775. },
  776. _reRender: function _reRender(options) {
  777. if (!this._rebuildPending) {
  778. if (this.ownerWidget.isVisible()) {
  779. var transactionToken = this._getRenderTransaction(options);
  780. // Only attempt to render requests with:
  781. // 1. no transaction token
  782. // 2. valid transaction token
  783. if (!transactionToken || this.transactionApi.isValidTransaction(transactionToken)) {
  784. return this.transactionApi.registerTransactionHandler(transactionToken || {}, this.ownerWidget.id, function (opt) {
  785. if (this.ownerWidget) {
  786. var visApi = this.ownerWidget.getAPI().getVisApi();
  787. return visApi.getRenderSequence().reRender(this._mergeOptions(opt));
  788. }
  789. return Promise.resolve();
  790. }.bind(this), options);
  791. }
  792. } else {
  793. this.ownerWidget.setReRenderOnShow(options);
  794. }
  795. }
  796. return Promise.resolve();
  797. },
  798. /**
  799. * @description This function will take the first object in the array and merge the 'refresh' parameters from the other objects into it
  800. * @param {Array} of render options
  801. * @returns {Object} the merged options object or the first object if there are no others to merge
  802. */
  803. _mergeOptions: function _mergeOptions(options) {
  804. var mergedOptions = options[0];
  805. var length = options.length;
  806. if (length > 1) {
  807. for (var i = 1; i < length; i++) {
  808. mergedOptions.refresh = this._mergeRefreshOptions(mergedOptions.refresh, options[i].refresh);
  809. if (options[i].refreshAll) {
  810. mergedOptions.refreshAll = options[i].refreshAll;
  811. }
  812. }
  813. }
  814. if (mergedOptions.refresh && mergedOptions.refresh.data && mergedOptions.refresh.dataIfQueryChanged) {
  815. // data refresh trumps dataIfQueryChanged
  816. delete mergedOptions.refresh.dataIfQueryChanged;
  817. }
  818. return mergedOptions;
  819. },
  820. _mergeRefreshOptions: function _mergeRefreshOptions(refreshOptions, options) {
  821. Object.keys(options).forEach(function (key) {
  822. if (!refreshOptions.hasOwnProperty(key)) {
  823. refreshOptions[key] = options[key];
  824. } else {
  825. refreshOptions[key] = refreshOptions[key] || options[key];
  826. }
  827. });
  828. return refreshOptions;
  829. },
  830. /**
  831. * Called when a filter on the VisModel changes.
  832. * By default....
  833. *
  834. * re-render the view when the event sender is not the same as the receiver
  835. * re-render the selections
  836. */
  837. // HANDLER: visModel.on('change:filters')
  838. onChangeFilter: function onChangeFilter(event) {
  839. var _this8 = this;
  840. event = event || {};
  841. var changeEvent = event.changeEvent;
  842. var transactionToken = this.transactionApi.startTransaction(changeEvent && changeEvent.data && changeEvent.data.transactionToken);
  843. var sender = event && event.sender;
  844. return VisUtil.validateVisDefinition(this.content, this.dashboardApi, { visId: this.ownerWidget.model.visId }).then(function (isValid) {
  845. if (isValid) {
  846. return _this8._queryChanged().then(function (result) {
  847. result = result || {};
  848. var refreshData = result.isRenderNeeded;
  849. var extraInfo = void 0;
  850. var renderOptions = {
  851. refresh: {
  852. dataIfQueryChanged: refreshData,
  853. annotation: true
  854. }
  855. };
  856. if (_this8.filterIndicator) {
  857. _this8.filterIndicator.setSynchronizeDataFilters(result.synchDataFilterEntries);
  858. _this8.filterIndicator.update();
  859. }
  860. if (!refreshData) {
  861. // optimization to avoid making unnecessary render requests
  862. if (!changeEvent || changeEvent.isInScope(_this8.ownerWidget.getScope(), _this8.ownerWidget.getEventGroupId())) {
  863. if (changeEvent && !changeEvent.brushingChanged() && _.every(changeEvent.getItems(), function (item) {
  864. return item.getValueCount() === 0;
  865. })) {
  866. // If the filter doesn't have any value and it is not a brushing event, do not rerender.
  867. return;
  868. }
  869. // @todo is this necessary?
  870. if (result.context) {
  871. result.context.visAPI = _this8.visAPI;
  872. }
  873. } else {
  874. _this8.visModel.renderComplete();
  875. return;
  876. }
  877. // ensure we don't introduce unnecessary animations upon re-render with no data change
  878. extraInfo = {
  879. entryDuration: 'zero',
  880. payloadData: { transactionToken: transactionToken }
  881. };
  882. }
  883. _this8._reRender(_this8.appendExtraInfoToRenderOptions(renderOptions, event, extraInfo)).then(function () {
  884. if (!refreshData) {
  885. _this8.dashboardApi.triggerDashboardEvent('dataInVis:selected', { payloadData: _this8.ownerWidget, sender: _this8.ownerWidget.id });
  886. var isOriginatorOfSelection = event.changeEvent && event.changeEvent.senderMatches(_this8.ownerWidget.id);
  887. //isOriginator means this widget is the originator of the selection change (not just one that is responding to the selection change).
  888. _this8.ownerWidget.trigger('visevent:selectionchanged', { sender: _this8.ownerWidget.id, isSelectionOriginator: isOriginatorOfSelection });
  889. _this8.renderFocused(!sender || sender === _this8.ownerWidget.id);
  890. }
  891. });
  892. });
  893. }
  894. }).finally(function () {
  895. _this8.transactionApi.endTransaction(transactionToken);
  896. });
  897. },
  898. _queryChanged: function _queryChanged() {
  899. // TODO Wrap to async result when use new query API. Need to be cleaned once switch to query api.
  900. if (this.ownerWidget.useNewQueryApi()) {
  901. var internalQueryExecution = this.content.getFeature('DataQueryExecution.internal');
  902. return internalQueryExecution.queryChanged().then(function (changed) {
  903. return { isRenderNeeded: changed };
  904. });
  905. } else {
  906. return this.visModel.queryChanged();
  907. }
  908. },
  909. toggleRefreshTimerIndicator: function toggleRefreshTimerIndicator(event) {
  910. if (this.refreshTimerIndicator.$icon) {
  911. if (event.autoRefresh) {
  912. this.refreshTimerIndicator.$icon.removeClass('dataWidgetTimersNone');
  913. } else {
  914. this.refreshTimerIndicator.$icon.addClass('dataWidgetTimersNone');
  915. }
  916. }
  917. },
  918. /**
  919. * Called when a filter that only affects this widget changes.
  920. * Such as from the filter dialog.
  921. *
  922. * re-render the selections
  923. */
  924. onChangeLocalFilter: function onChangeLocalFilter(event) {
  925. var renderOptions = {
  926. refresh: {
  927. data: true,
  928. annotation: true
  929. }
  930. };
  931. this.ownerWidget.updateMissingFilters();
  932. /* vis isn't tied to data anymore*/
  933. if (this._isViewSlotsEmpty()) {
  934. this._rebuildVis(event, this.appendExtraInfoToRenderOptions(renderOptions, event));
  935. return;
  936. }
  937. this.visModel.setPendingFilters(false); //cancel any pending filters
  938. var sender = event && event.sender;
  939. this._reRender(this.appendExtraInfoToRenderOptions(renderOptions, event));
  940. this.renderFocused(!sender || sender === this.ownerWidget.id);
  941. if (this.filterIndicator) {
  942. this.filterIndicator.update();
  943. }
  944. },
  945. onChangePendingFilter: function onChangePendingFilter() {
  946. this.visModel.setPendingFilters(true);
  947. },
  948. /**
  949. * Slots include vis slots and local filters slots
  950. */
  951. _isViewSlotsEmpty: function _isViewSlotsEmpty() {
  952. return !this.visualization.getSlots().getMappedSlotList().length && this.visModel.getLocalFilters().isEmpty();
  953. },
  954. _annotationHasChanged: function _annotationHasChanged(event) {
  955. var prev = event && event.changeEvent && event.changeEvent.prevValue;
  956. var curr = event && event.changeEvent && event.changeEvent.value;
  957. if (prev && prev.selectedAnnotations && prev.selectedAnnotations.length || curr && curr.selectedAnnotations && curr.selectedAnnotations.length) {
  958. return true;
  959. } else {
  960. return false;
  961. }
  962. },
  963. onChangeAnnotations: function onChangeAnnotations(event) {
  964. if (this._annotationHasChanged(event)) {
  965. var renderOptions = {
  966. refresh: {
  967. dataIfQueryChanged: true,
  968. annotation: true
  969. },
  970. extraInfo: {
  971. annotationRequest: true
  972. }
  973. };
  974. return this._reRender(this.appendExtraInfoToRenderOptions(renderOptions, event));
  975. }
  976. return Promise.resolve();
  977. },
  978. onVisible: function onVisible() {},
  979. isVisible: function isVisible() {
  980. return this.$el.parent().is(':visible');
  981. },
  982. /**
  983. * Called when a property (like a font or a colour) changes.
  984. * By default....
  985. * re-render the view
  986. * re-render the selections
  987. * Set queryManager and render options if one of the properties requiring data refresh have changed (currently maintainAxisScales)
  988. * @param event - an event containing the single property from the property collection that changed (eg: 'showAxisTitles').
  989. */
  990. // HANDLER: visModel.on('change:property')
  991. onChangeProperties: function onChangeProperties(modelEvent) {
  992. var event = {
  993. name: 'properties',
  994. value: modelEvent && modelEvent.value,
  995. sender: null,
  996. changeEvent: modelEvent,
  997. refreshData: modelEvent && modelEvent.origCollectionEvent && modelEvent.origCollectionEvent.dataRefresh
  998. };
  999. var renderOptions = {
  1000. refresh: {}
  1001. };
  1002. if (event && event.refreshData || renderOptions.refresh.data) {
  1003. if (_.isEmpty(renderOptions.refresh)) {
  1004. renderOptions.refresh.data = true;
  1005. }
  1006. }
  1007. // re-render the visualization
  1008. var reRenderOptions = this.appendExtraInfoToRenderOptions(renderOptions, event);
  1009. this._reRender(reRenderOptions);
  1010. },
  1011. /**
  1012. * Called when the conditional formatting changes
  1013. */
  1014. onChangeConditions: function onChangeConditions(event) {
  1015. var renderOptions = {
  1016. refresh: {}
  1017. };
  1018. this._reRender(this.appendExtraInfoToRenderOptions(renderOptions, event));
  1019. },
  1020. /**
  1021. * Called when the container is entered.
  1022. */
  1023. onEnterContainer: function onEnterContainer() {
  1024. /* to be overridden */
  1025. },
  1026. /**
  1027. * Called when the container is exited.
  1028. */
  1029. onExitContainer: function onExitContainer() {
  1030. /* to be overridden */
  1031. },
  1032. /**
  1033. * update animation settings for transitions from one state to another according to the following rules:
  1034. * 1) use the default defined in the definition properties unless override is set to exactly 0 (to turn it off)
  1035. * 2) if no animation properties in the definition, use the override value if its > 0
  1036. * 3) if not in the def properties and not overridden by an action, animation should be off.
  1037. * NOTE: by default overrideDefaultAnimationSpeed is -1 which denotes it is not enabled.
  1038. */
  1039. updateAnimationSettings: function updateAnimationSettings(visdefProperties) {
  1040. var visDefAnimationSpeed = visdefProperties && visdefProperties.animationSpeed ? visdefProperties.animationSpeed : 0;
  1041. this.animationSpeed = visDefAnimationSpeed ? visDefAnimationSpeed : this.DEFAULT_ANIMATION_SPEED;
  1042. if (this.overrideDefaultAnimationSpeed === 0 || this.visModel.getSuppressViewAnimations()) {
  1043. //If an action has explicitly turned animation off, respect that option.
  1044. this.animationSpeed = 0;
  1045. } else if (this.overrideDefaultAnimationSpeed > 0 && visDefAnimationSpeed === 0) {
  1046. //If animation is not on, use the override if its not off (-1)
  1047. this.animationSpeed = this.overrideDefaultAnimationSpeed;
  1048. }
  1049. },
  1050. /**
  1051. * VIPR supports animation effects (which are always enabled)
  1052. * It is possible to do a fade-in/fade-out type animation for non-VIPR visualizations.
  1053. * In this case, renderComplete should be called by animate not render.
  1054. * NOTE: This style of animation has been DISABLED for Endor and is likely to be removed/reworked in future
  1055. * as it simply makes non-VIPR visualizations flash needlessly (and appear slower).
  1056. * @returns true if non-VIPR animation has been enabled for a particular visualization type
  1057. */
  1058. isAnimationEnabledForNonVIPRVisualizations: function isAnimationEnabledForNonVIPRVisualizations() {
  1059. return this.animationType && this.animationType !== this.ANIMATION_TYPES.NONE;
  1060. },
  1061. /**
  1062. * For now, for grid/summary/trend etc, there is only one animation effect by default which is to fade out and fade in
  1063. */
  1064. animate: function animate(renderInfo) {
  1065. var _this9 = this;
  1066. this.visModel.renderCompleteBeforeAnimation();
  1067. if (this.isAnimationEnabledForNonVIPRVisualizations()) {
  1068. var renderComplete = function renderComplete() {
  1069. _this9.visModel.renderComplete(renderInfo);
  1070. };
  1071. this.updateAnimationSettings();
  1072. $(this.el).fadeOut(0);
  1073. //If doing fade-in/fade-out animation, we need to call render complete at the end.
  1074. $(this.el).fadeIn(this.animationSpeed, renderComplete);
  1075. }
  1076. },
  1077. /**
  1078. * Override this method to take the selection and highlight it.
  1079. */
  1080. renderSelected: function renderSelected() {},
  1081. /**
  1082. * Override this method to style focused item.
  1083. */
  1084. renderFocused: function renderFocused() {},
  1085. /**
  1086. * The base render method is called at the end of the subtype render() to provide any actions required
  1087. * once render is prepared and submitted to the rendering engine (eg: RAVE).
  1088. *
  1089. * Its important to note that due to the async nature of rendering, this method may be called prior
  1090. * to the actual render being completed.
  1091. * @param {Object} renderInfo - renderInfo passed to all render methods (originates in the render sequence).
  1092. * @param {boolean default=true} callRenderComplete - true if this function is being called at the end of the type-specific view render
  1093. * so that renderComplete will be called. VIPRView is an exception.
  1094. * VIPRView calls VisView:render to do common processing but needs to handle renderComplete itself because of the way it sets/decorates data.
  1095. */
  1096. render: function render(renderInfo) {
  1097. var callRenderComplete = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
  1098. this.animate(renderInfo); //Note: animate is overridden to do nothing for RAVE as it has effects support
  1099. this.updateConditionalViewIndicator({
  1100. data: renderInfo.data
  1101. });
  1102. if (callRenderComplete && !this.isAnimationEnabledForNonVIPRVisualizations()) {
  1103. //If animation is enabled, we need to wait until the end of the animation to call render complete.
  1104. //See (VisView:animate()). NOTE: This style of animation is disabled for Endor.
  1105. this.visModel.renderComplete(renderInfo);
  1106. }
  1107. return Promise.resolve(this);
  1108. },
  1109. /**
  1110. * Removes the icon view element and remove hide class from the rave output area whose role is application
  1111. */
  1112. removeIconView: function removeIconView() {
  1113. if (!(this.hasUnavailableMetadataColumns() || this.hasMissingFilters())) {
  1114. this._removeMissingDataColumnWarning();
  1115. }
  1116. if (this.$iconView) {
  1117. this.$el.find('div[role="application"]').removeClass('hide');
  1118. this.$el.parent().removeClass('showIconView');
  1119. this.$iconView.remove();
  1120. this.$iconView = null;
  1121. }
  1122. this._removeVisualizationDropZones();
  1123. },
  1124. _removeVisualizationDropZones: function _removeVisualizationDropZones() {
  1125. var dropZonesOverlayState = this.content.getFeature('DropZonesOverlayState');
  1126. if (dropZonesOverlayState && dropZonesOverlayState.isEnabled()) {
  1127. dropZonesOverlayState.hide();
  1128. }
  1129. },
  1130. /**
  1131. * Render warning container for this widget visualization
  1132. */
  1133. _renderWarningContainer: function _renderWarningContainer($containerNode) {
  1134. this._removeWarningContainer();
  1135. this.$warningContainer = $('<div></div>', {
  1136. 'class': 'warningContainer'
  1137. });
  1138. $containerNode.append(this.$warningContainer);
  1139. },
  1140. /**
  1141. * Remove warning container
  1142. */
  1143. _removeWarningContainer: function _removeWarningContainer() {
  1144. if (this.$warningContainer) {
  1145. this.$warningContainer.remove();
  1146. this.$warningContainer = null;
  1147. }
  1148. },
  1149. _renderMissingDataColumnWarning: function _renderMissingDataColumnWarning($containerNode) {
  1150. this._removeMissingDataColumnWarning();
  1151. if (this.hasMissingFilters() && this.filterIndicator && this.filterIndicator.$filter) {
  1152. this.filterIndicator.$filter.addClass('dataWidgetFiltersNone');
  1153. }
  1154. this.$dataUnavailable = $('<div></div>', {
  1155. 'class': 'data-unavailable'
  1156. });
  1157. this._renderWarningContainer($containerNode);
  1158. this.$warningContainer.append(this.$dataUnavailable);
  1159. var $warningIcon = $('<div></div>', {
  1160. 'aria-label': StringResources.get('warning'),
  1161. 'class': 'warningIcon'
  1162. });
  1163. this.$dataUnavailable.append($warningIcon);
  1164. Utils.setIcon($warningIcon, 'common-warning', StringResources.get('warning'));
  1165. var $warningText = $('<div></div>', {
  1166. 'class': 'warningText',
  1167. 'text': StringResources.get('datasetItemsUnavailable')
  1168. });
  1169. this.$dataUnavailable.append($warningText);
  1170. },
  1171. _removeMissingDataColumnWarning: function _removeMissingDataColumnWarning() {
  1172. if (this.$dataUnavailable) {
  1173. this.$dataUnavailable.remove();
  1174. this.$dataUnavailable = null;
  1175. }
  1176. this._removeWarningContainer();
  1177. },
  1178. /**
  1179. * Shows icon of the chart type and hide the rave output area whose role is application
  1180. */
  1181. renderIconView: function renderIconView() {
  1182. if (this._tabs) {
  1183. this._tabs.hide();
  1184. }
  1185. var $containerNode = this._prepareRenderIconView();
  1186. this._renderIconView($containerNode);
  1187. this._renderVisualizationDropZones();
  1188. },
  1189. _renderVisualizationDropZones: function _renderVisualizationDropZones() {
  1190. var dropZonesOverlayDOM = this.content.getFeature('DropZonesOverlayDOM');
  1191. var dropZonesOverlayState = this.content.getFeature('DropZonesOverlayState');
  1192. if (dropZonesOverlayState && dropZonesOverlayState.isEnabled() && !this.hasMissingFilters()) {
  1193. if (dropZonesOverlayDOM.isMounted()) {
  1194. dropZonesOverlayState.show();
  1195. } else {
  1196. dropZonesOverlayDOM.render();
  1197. }
  1198. }
  1199. },
  1200. /**
  1201. * Updates the smart annotations indicator if one exists
  1202. */
  1203. updateSmartAnnotationsIndicator: function updateSmartAnnotationsIndicator() {
  1204. var smartAnnotationsIndicator = this.content.getFeature('SmartsIndicator');
  1205. if (smartAnnotationsIndicator) {
  1206. smartAnnotationsIndicator.update();
  1207. }
  1208. },
  1209. updateConditionalViewIndicator: function updateConditionalViewIndicator(options) {
  1210. var conditionalViewIndicator = this.content.getFeature('ConditionalViewIndicator');
  1211. if (conditionalViewIndicator) {
  1212. conditionalViewIndicator.update(options);
  1213. }
  1214. },
  1215. /**
  1216. * Updates the forecast indicator if one exists
  1217. */
  1218. updateForecastIndicator: function updateForecastIndicator() {
  1219. if (this.forecastIndicator) {
  1220. this.forecastIndicator.update();
  1221. }
  1222. },
  1223. /*Subclass can override the function when necessary*/
  1224. _renderIconView: function _renderIconView($containerNode) {
  1225. $containerNode.append(this.$iconView);
  1226. this.$el.find('div[role="application"]').addClass('hide');
  1227. this.$el.parent().addClass('showIconView');
  1228. this.visModel.renderCompleteBeforeAnimation();
  1229. this.visModel.renderComplete();
  1230. },
  1231. /**
  1232. * Returns the content node. Can be overriden by sub classes.
  1233. */
  1234. getContentNode: function getContentNode() {
  1235. return this.contentNode ? $(this.contentNode) : this.$el;
  1236. },
  1237. _prepareRenderIconView: function _prepareRenderIconView() {
  1238. var definition = this.visualization.getDefinition();
  1239. var icon = definition.getPlaceholderIconUri();
  1240. var caption = definition.getLabel();
  1241. var $containerNode = this.getContentNode();
  1242. if (this.hasUnavailableMetadataColumns() || this.hasMissingFilters()) {
  1243. this._renderMissingDataColumnWarning($containerNode);
  1244. } else if (this.$dataUnavailable) {
  1245. this._removeMissingDataColumnWarning();
  1246. }
  1247. var template = dot.template(EmptyVisualizationTemplate);
  1248. if (this.$iconView) {
  1249. this.$iconView.remove();
  1250. }
  1251. this.$iconView = $(template({
  1252. icon: icon,
  1253. caption: caption,
  1254. title: StringResources.get('LIVE_empty_visualization_hint_title'),
  1255. description: StringResources.get('LIVE_empty_visualization_hint_description')
  1256. }));
  1257. return $containerNode;
  1258. },
  1259. hasMissingFilters: function hasMissingFilters() {
  1260. return this.ownerWidget.getUnavailableLocalFilter().length > 0;
  1261. },
  1262. /**
  1263. * Uses the widget size in a given render context
  1264. */
  1265. resizeToWidget: function resizeToWidget(renderInfo) {
  1266. this.resize(renderInfo.widgetSize);
  1267. },
  1268. getLabel: function getLabel() {
  1269. var label;
  1270. var definition = this.visualization.getDefinition();
  1271. if (definition) {
  1272. label = definition.getLabel();
  1273. } else {
  1274. label = StringResources.get('visualizationLabel');
  1275. }
  1276. return label;
  1277. },
  1278. getDescription: function getDescription() {
  1279. var label = this.getLabel();
  1280. var columns = this._getColumnInformationForLabel();
  1281. var description = StringResources.get('dataWidgetDescription', {
  1282. widgetLabel: label,
  1283. columnNames: columns
  1284. });
  1285. return description;
  1286. },
  1287. _getColumnInformationForLabel: function _getColumnInformationForLabel() {
  1288. var columns = '';
  1289. var mappingInfo = this.visualization.getSlots().getMappingInfoList();
  1290. for (var i = 0; i < mappingInfo.length; i++) {
  1291. if (i > 0) {
  1292. columns += ', ';
  1293. }
  1294. columns += mappingInfo[i].dataItem.getLabel();
  1295. }
  1296. return columns;
  1297. },
  1298. getCurrentViewSelector: function getCurrentViewSelector() {
  1299. return null;
  1300. },
  1301. getProperties: function getProperties() {
  1302. if (this.visModel.getDefinition().properties) {
  1303. return Promise.resolve(this.visModel.getDefinition().properties.slice(0));
  1304. }
  1305. return Promise.resolve([]);
  1306. },
  1307. /**
  1308. * @returns true if any any dataItems is binned
  1309. */
  1310. hasBinnedDataItems: function hasBinnedDataItems() {
  1311. return !!this.visualization.getSlots().getMappingInfoList().find(function (mappingInfo) {
  1312. return !!mappingInfo.dataItem.getBinning();
  1313. });
  1314. },
  1315. /**
  1316. * Generate thumbnail
  1317. *
  1318. * @return {Promise}
  1319. */
  1320. generateThumbnail: function generateThumbnail() {
  1321. var _this10 = this;
  1322. var isDisabled = false;
  1323. var isMappingIncomplete = false;
  1324. if (this._isThumbnailDisabled()) {
  1325. isDisabled = true;
  1326. }
  1327. if (!this.isMappingComplete()) {
  1328. isMappingIncomplete = true;
  1329. }
  1330. var promise = void 0;
  1331. if (isDisabled || isMappingIncomplete) {
  1332. promise = Promise.resolve();
  1333. } else {
  1334. promise = this.dashboardApi.getDashboardSvc('.Thumbnail').then(function (thumbnailSvc) {
  1335. return thumbnailSvc.generateMarkup(_this10.$el.get(0));
  1336. });
  1337. }
  1338. return promise.then(function (thumbnail) {
  1339. return {
  1340. isMappingIncomplete: isMappingIncomplete,
  1341. isDisabled: isDisabled,
  1342. thumbnail: thumbnail
  1343. };
  1344. });
  1345. },
  1346. _isThumbnailDisabled: function _isThumbnailDisabled() {
  1347. //TODO: Augment this to disable thumbnail for any declared unsupportedBrowsers
  1348. // disable thumbnails only for IE11 and lower; Edge is supposedly behaving good in most cases
  1349. if (BrowserUtils.isIE() && !BrowserUtils.isIEEdge()) {
  1350. var defn = this.visModel.getDefinition();
  1351. if (defn && defn.thumbnailConfig) {
  1352. //Disable thumbnails for visualizaions that delcares not supporting certain browsers
  1353. var _defn$thumbnailConfig = defn.thumbnailConfig.unsupportedBrowsers,
  1354. unsupportedBrowsers = _defn$thumbnailConfig === undefined ? [] : _defn$thumbnailConfig;
  1355. return unsupportedBrowsers.indexOf('IE') !== -1;
  1356. }
  1357. }
  1358. return false;
  1359. },
  1360. onSummaryDataCellError: function onSummaryDataCellError() {
  1361. var errorMsg = StringResources.get('errorCellWarning');
  1362. this.infoIndicator.addInfo([{
  1363. id: 'error_summary_cell',
  1364. title: errorMsg,
  1365. items: [{
  1366. id: 'error_summary_cell',
  1367. label: errorMsg,
  1368. isSubMessage: true
  1369. }]
  1370. }]);
  1371. },
  1372. appendExtraInfoToRenderOptions: function appendExtraInfoToRenderOptions(renderOptions, event, extraInfo) {
  1373. event = event && event.changeEvent || event;
  1374. if (event && event.data) {
  1375. if (renderOptions.extraInfo) {
  1376. renderOptions.extraInfo.payloadData = event.data;
  1377. } else {
  1378. renderOptions.extraInfo = {
  1379. payloadData: event.data
  1380. };
  1381. }
  1382. }
  1383. if (renderOptions && renderOptions.extraInfo && extraInfo) {
  1384. renderOptions.extraInfo = _.extend(renderOptions.extraInfo, extraInfo);
  1385. }
  1386. return renderOptions;
  1387. }
  1388. });
  1389. return VisView;
  1390. });
  1391. //# sourceMappingURL=VisView.js.map