LayoutBaseView.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. 'use strict';
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2013, 2020
  5. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  6. */
  7. define(['../../../../lib/@waca/core-client/js/core-client/ui/core/Class', '../../LayoutHelper', 'jquery', 'underscore', './feature/CommonAuthoringFeature'], function (Base, LayoutHelper, $, _, CommonAuthoringFeature) {
  8. var BaseLayout = null;
  9. BaseLayout = Base.extend({
  10. init: function init(options) {
  11. BaseLayout.inherited('init', this, arguments);
  12. this.consumeView = options.consumeView; // the consume view
  13. this.overridden = {}; // container for functions overridden in consume view
  14. this.added = []; //track all 'new' added functions in consume view
  15. //{setup helpers ... initialize from consume view
  16. this.domNode = this.consumeView.domNode;
  17. this.id = this.consumeView.id;
  18. this.layoutController = this.consumeView.layoutController;
  19. this.model = this.consumeView.model;
  20. this.getNodeId = this.consumeView.getNodeId.bind(this.consumeView);
  21. //}setup helpers
  22. //override functions in consume view
  23. this.specializeConsumeView(['add', 'removeChild', 'onResize', 'onResizeStep', 'getWidgetPositionUpdate', 'getMinimumTop', 'getMinimumLeft', 'getMaximumHeight', 'getMaximumWidth', 'getPhysicalPageSize', 'onShow']);
  24. this.consumeView.authorHelper = this;
  25. this.consumeView.isAuthoringMode = true;
  26. this.commonAuthoringFeature = new CommonAuthoringFeature(this.consumeView);
  27. this.dashboardApi = options.dashboardApi;
  28. this.$el = $(this.domNode);
  29. },
  30. specializeConsumeView: function specializeConsumeView(funcNames) {
  31. _.each(funcNames, function (funcName) {
  32. if (this.consumeView[funcName] && this.added.indexOf(funcName) === -1) {
  33. if (!this.overridden[funcName]) {
  34. //save original if we have not already
  35. this.overridden[funcName] = this.consumeView[funcName].bind(this.consumeView);
  36. }
  37. } else {
  38. this.added.push(funcName);
  39. }
  40. //add specialized func
  41. this.consumeView[funcName] = this[funcName].bind(this);
  42. }.bind(this));
  43. },
  44. restoreSpecializedFunc: function restoreSpecializedFunc(funcName) {
  45. if (this.overridden[funcName]) {
  46. this.consumeView[funcName] = this.overridden[funcName];
  47. }
  48. },
  49. restoreAllSpecialized: function restoreAllSpecialized() {
  50. for (var name in this.overridden) {
  51. if (this.overridden.hasOwnProperty(name)) {
  52. this.restoreSpecializedFunc(name);
  53. }
  54. }
  55. },
  56. removeAllAddedFuncs: function removeAllAddedFuncs() {
  57. for (var i = 0; i < this.added.length; i++) {
  58. if (this.consumeView[this.added[i]]) {
  59. this.consumeView[this.added[i]] = null;
  60. }
  61. }
  62. },
  63. renderContent: function renderContent() {
  64. return this.overridden.renderContent();
  65. },
  66. onResize: function onResize(renderOptions) {
  67. this.overridden.onResize(renderOptions);
  68. },
  69. onShow: function onShow() {
  70. this.overridden.onShow(arguments);
  71. },
  72. /**
  73. * Called when a resize starts and continuously called for each resize step
  74. *
  75. */
  76. onResizeStep: function onResizeStep() {
  77. this.commonAuthoringFeature.onResizeStep();
  78. },
  79. destroy: function destroy() {
  80. BaseLayout.inherited('destroy', this, arguments);
  81. this.restoreAllSpecialized();
  82. this.removeAllAddedFuncs();
  83. this.commonAuthoringFeature.destroy();
  84. this.consumeView.isAuthoringMode = false;
  85. },
  86. /**
  87. * Add a node to the view
  88. * @param layoutView
  89. * @param insertBeforeId
  90. */
  91. add: function add(layoutView, insertBeforeId) {
  92. this.consumeView._subLayoutViews.push(layoutView);
  93. var node = layoutView.domNode;
  94. if (!node) {
  95. return;
  96. }
  97. if (node.parentNode) {
  98. node.parentNode.removeChild(node);
  99. }
  100. var insertBeforeNode = insertBeforeId ? $('#' + this.getNodeId(insertBeforeId), this.layoutController.$el)[0] : null;
  101. this.domNode.insertBefore(node, insertBeforeNode);
  102. },
  103. /**
  104. * Remove a layout from the view
  105. * @param node
  106. *
  107. */
  108. removeChild: function removeChild(layoutView) {
  109. this.consumeView._subLayoutViews = this.consumeView._subLayoutViews.filter(function (view) {
  110. return view !== layoutView;
  111. });
  112. // Simply detach and not remove(). We might be moving the layout to a different parent.
  113. if (layoutView && layoutView.domNode) {
  114. $(layoutView.domNode).detach();
  115. }
  116. },
  117. /**
  118. * Return the model update object that represent the state of the given node info
  119. */
  120. getWidgetPositionUpdate: function getWidgetPositionUpdate(nodeInfo) {
  121. var update = null;
  122. if (nodeInfo.node._layout) {
  123. var siblingId = nodeInfo.dropPosition.before && nodeInfo.dropPosition.before._layout ? nodeInfo.dropPosition.before._layout.model.id : null;
  124. var xPosition = nodeInfo.dropPosition.x;
  125. var yPosition = nodeInfo.dropPosition.y;
  126. var $node = $(nodeInfo.node);
  127. var width = xPosition + $node.width();
  128. var maxWidth = this.getMaximumWidth();
  129. if (maxWidth && width > maxWidth) {
  130. xPosition = maxWidth - $node.width();
  131. }
  132. var height = yPosition + $node.height();
  133. var maxHeight = this.getMaximumHeight();
  134. if (maxHeight && height > maxHeight) {
  135. yPosition = maxHeight - $node.height();
  136. }
  137. update = {
  138. style: {
  139. left: Math.max(this.getMinimumLeft(), xPosition) + 'px',
  140. top: Math.max(this.getMinimumTop(), yPosition) + 'px'
  141. },
  142. insertBefore: siblingId,
  143. parentId: this.model.id,
  144. id: nodeInfo.node._layout.model.id
  145. };
  146. if (nodeInfo.dropPosition.height) {
  147. update.style.height = nodeInfo.dropPosition.height + 'px';
  148. }
  149. if (nodeInfo.dropPosition.width) {
  150. update.style.width = nodeInfo.dropPosition.width + 'px';
  151. }
  152. }
  153. return update;
  154. },
  155. getMaximumWidth: function getMaximumWidth() {
  156. var pageSize = this.getPhysicalPageSize();
  157. if (pageSize) {
  158. return pageSize.width;
  159. } else {
  160. return Infinity;
  161. }
  162. },
  163. getMaximumHeight: function getMaximumHeight() {
  164. var pageSize = this.getPhysicalPageSize();
  165. if (pageSize) {
  166. return pageSize.height;
  167. } else {
  168. return Infinity;
  169. }
  170. },
  171. getMinimumTop: function getMinimumTop() {
  172. var paddingTop = parseInt(this.$el.css('padding-top'), 10);
  173. return isNaN(paddingTop) ? 0 : paddingTop;
  174. },
  175. getMinimumLeft: function getMinimumLeft() {
  176. var paddingLeft = parseInt(this.$el.css('padding-left'), 10);
  177. return isNaN(paddingLeft) ? 0 : paddingLeft;
  178. },
  179. //general helper
  180. _addWidget: function _addWidget(widgetSpec, isTouch) {
  181. var _this = this;
  182. var transactionApi = this.dashboardApi.getFeature('Transaction');
  183. var transactionToken = transactionApi.startTransaction();
  184. var widgetModel = widgetSpec.model;
  185. var content = {
  186. containerId: widgetSpec.parentId,
  187. type: 'widget.' + widgetModel.type,
  188. spec: widgetModel,
  189. properties: widgetSpec.layoutProperties.style
  190. };
  191. return this.dashboardApi.getCanvas().addContent(content, transactionToken).then(function (contentApi) {
  192. transactionApi.endTransaction(transactionToken);
  193. return _this.layoutController.whenWidgetRenderComplete(contentApi.getId()).done(function (layout) {
  194. var selectionHandler = _this.layoutController.interactionController.selectionHandler;
  195. selectionHandler.deselectAll();
  196. // pass the flag isTouch to properly render the resize edges for touch device.
  197. selectionHandler.selectNode(layout.domNode, {
  198. 'isTouch': isTouch
  199. });
  200. });
  201. });
  202. },
  203. _getModelToAddFromDragObject: function _getModelToAddFromDragObject(dragObject) {
  204. var canvasDnD = this.dashboardApi.getFeature('CanvasDnD');
  205. return canvasDnD.onDrop(dragObject);
  206. },
  207. _processWidgetSpecForPin: function _processWidgetSpecForPin(widgetSpec, isTouch) {
  208. if (widgetSpec.contentType === 'boardFragment') {
  209. var fragmentModel = {
  210. layout: widgetSpec.content.layout,
  211. widgets: widgetSpec.content.widgets,
  212. dataSources: widgetSpec.content.dataSources,
  213. properties: widgetSpec.content.properties,
  214. sourceName: widgetSpec.sourceName
  215. };
  216. if (widgetSpec.content.drillThrough) {
  217. fragmentModel.drillThrough = widgetSpec.content.drillThrough;
  218. }
  219. if (widgetSpec.content.episodes) {
  220. fragmentModel.episodes = widgetSpec.content.episodes;
  221. }
  222. var options = {
  223. model: fragmentModel,
  224. parentId: widgetSpec.parentId
  225. };
  226. var payload = this.model.boardModel.addFragment(options, 'layoutBaseView_onPinDrop', null);
  227. // The newly added widget from pin needs to be selected after finishing rendering
  228. if (payload && payload.value && payload.value.parameter && payload.value.parameter.model.layout && payload.value.parameter.model.layout.id) {
  229. this.layoutController.whenWidgetRenderComplete(payload.value.parameter.model.layout.id).done(function (layout) {
  230. var selectionHandler = this.layoutController.interactionController.selectionHandler;
  231. selectionHandler.deselectAll();
  232. // pass the flag isTouch to properly render the resize edges for touch device.
  233. selectionHandler.selectNode(layout.domNode, {
  234. 'isTouch': isTouch
  235. });
  236. }.bind(this));
  237. }
  238. } else {
  239. this._addWidget(widgetSpec, isTouch);
  240. }
  241. this.dashboardApi.getFeature('Colors').clearLocalPaletteCache();
  242. },
  243. /**
  244. * Returns the displayable page size for the view (in pixels)
  245. */
  246. getPhysicalPageSize: function getPhysicalPageSize(layoutPositioning, id) {
  247. var layoutPos = layoutPositioning || this.model.getLayoutPositioning(true);
  248. if (layoutPos === 'absolute') {
  249. // The display page size in absolute is the configured page size
  250. return this.model.boardModel.layout.get('pageSize');
  251. } else if (layoutPos === 'relative') {
  252. // The display page size is just a ratio in relative, calculate the page size
  253. return this._getRelativePhysicalPageSize(id);
  254. } else {
  255. return { width: this.domNode.offsetWidth, height: this.domNode.offsetHeight };
  256. }
  257. },
  258. _getRelativePhysicalPageSize: function _getRelativePhysicalPageSize(id) {
  259. var $firstVisiblePage = void 0,
  260. pageSize = {};
  261. if (this.$el.is(':visible')) {
  262. // Use the page if it is visible
  263. pageSize = { width: this.$el.width(), height: this.$el.height() };
  264. } else {
  265. // Page is not visible
  266. if (id) {
  267. $firstVisiblePage = $(LayoutHelper.getLayoutContentContainer(this.layoutController.topLayoutModel.id, id, this.dashboardApi));
  268. } else {
  269. $firstVisiblePage = $(this.layoutController.getLayoutContentContainer());
  270. }
  271. if ($firstVisiblePage.length) {
  272. var firstVisiblePageLayoutPos = $firstVisiblePage[0]._layout.model.getLayoutPositioning(true);
  273. if (firstVisiblePageLayoutPos === 'relative') {
  274. // If the first visible page is the same type as this page, then use its page size
  275. pageSize = { width: $firstVisiblePage.width(), height: $firstVisiblePage.height() };
  276. } else {
  277. // The dashboard has not be upgraded to the new layout (mixed layouts on tabs)
  278. // Current page layout is relative and first visible page is absolute
  279. // Calculate current relative pageSize using relative ratio on visible absolute page size
  280. var $absoluteContainer = $firstVisiblePage.parent();
  281. var currentPageSize = this.model.boardModel.layout.get('pageSize');
  282. pageSize = {
  283. width: $absoluteContainer.width(),
  284. height: $absoluteContainer.width() * (currentPageSize.height / currentPageSize.width)
  285. };
  286. }
  287. } else {
  288. // No visible pages to base the page size off, we shouldn't be here
  289. return { width: this.domNode.offsetWidth, height: this.domNode.offsetHeight };
  290. }
  291. }
  292. return pageSize;
  293. },
  294. /**This function with reposition a widget if it is outside the bounds of the
  295. dashboard
  296. * @param maxHeight - maximum height of the layout specified in style
  297. * @param maxWidth - maximum width of the layout specified in style
  298. * @param style - expected to have height and width properties to compare with
  299. maxHeight and maxWidth. Will be resized to fit.
  300. */
  301. moveToFitBoundaries: function moveToFitBoundaries(maxHeight, maxWidth, style) {
  302. // widgets placed outside of layout will be bumped in
  303. if (style.top + style.height > maxHeight) {
  304. style.top = maxHeight - style.height;
  305. }
  306. if (style.left + style.width > maxWidth) {
  307. style.left = maxWidth - style.width;
  308. }
  309. },
  310. /**
  311. * Update the model for this view
  312. * @public
  313. * @function LayoutBaseView#updateModel
  314. * @param {Object[]} styleUpdateArray Styles to be updated
  315. * @param {TransactionToken} transactionToken transactionToken
  316. * @param {boolean} [validate = true] Flag the indicating whether to validate the styles
  317. */
  318. updateModel: function updateModel(styleUpdateArray, transactionToken) {
  319. var _this2 = this;
  320. var validate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
  321. styleUpdateArray.forEach(function (styleUpdate) {
  322. var content = _this2.dashboardApi.getCanvas().getContent(styleUpdate.id);
  323. var options = Object.assign({}, transactionToken, { validatePropertyValue: false });
  324. Object.keys(styleUpdate.style).forEach(function (propertyName) {
  325. content.setPropertyValue(propertyName, styleUpdate.style[propertyName], options);
  326. });
  327. //we have to do this twice so we know the properties are all set before validating.
  328. options.validatePropertyValue = validate;
  329. Object.keys(styleUpdate.style).forEach(function (propertyName) {
  330. content.setPropertyValue(propertyName, styleUpdate.style[propertyName], options);
  331. });
  332. });
  333. }
  334. });
  335. return BaseLayout;
  336. });
  337. //# sourceMappingURL=LayoutBaseView.js.map