LayoutHelper.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. 'use strict';
  2. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  3. /**
  4. * Licensed Materials - Property of IBM
  5. * IBM Cognos Products: Dashboard (C) Copyright IBM Corp. 2015, 2020
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. */
  8. define(['jquery', 'underscore', '../../app/util/DeepClone'], function ($, _) {
  9. var LayoutHelper = function () {
  10. function LayoutHelper() {
  11. _classCallCheck(this, LayoutHelper);
  12. }
  13. LayoutHelper.setPreferredAddOptions = function setPreferredAddOptions(addOptions, topLayoutModel, dashboardApi) {
  14. // if we have no parent Id, we will add to the last visible page
  15. if (!addOptions.parentId) {
  16. var lastPage = LayoutHelper.getLastVisiblePage(topLayoutModel.id, null, dashboardApi);
  17. if (lastPage) {
  18. addOptions.parentId = LayoutHelper.nodeIdToModelId(lastPage.id);
  19. }
  20. }
  21. var sourceModel = addOptions.model;
  22. LayoutHelper.setPreferredLocation(addOptions);
  23. if (addOptions.copyPaste) {
  24. LayoutHelper._setCopyPasteLayout(addOptions, sourceModel, topLayoutModel, dashboardApi);
  25. }
  26. };
  27. LayoutHelper.setPreferredLocation = function setPreferredLocation(addOptions) {
  28. // Ask the layout to update the layout properties with preferred options (e.g. position)
  29. if (addOptions.parentId) {
  30. // find the layout object
  31. var nLayout = $('#' + LayoutHelper.modelIdToNodeId(addOptions.parentId), this.$el)[0];
  32. if (nLayout && nLayout._layout && nLayout._layout.setPreferredLocation) {
  33. if (!addOptions.layoutProperties) {
  34. addOptions.layoutProperties = {};
  35. }
  36. nLayout._layout.setPreferredLocation(addOptions);
  37. }
  38. }
  39. };
  40. /**
  41. * Paste a widget to current page via using the copy-paste functionality
  42. * the pasted widget into the current page will be the same size as the source widget
  43. * Its position will be slightly offset from the source widget
  44. * the insertBefore id is null (because we want it pasted in front of the source widget).
  45. * @addOptions
  46. * @sourceModel the copied model
  47. */
  48. LayoutHelper._setCopyPasteLayout = function _setCopyPasteLayout(addOptions, sourceModel, topLayoutModel, dashboardApi) {
  49. var currentPage = LayoutHelper.getLayoutContentContainer(topLayoutModel.id, null, dashboardApi);
  50. var currentPageId = LayoutHelper.nodeIdToModelId(currentPage.id);
  51. var topItem = topLayoutModel.findModel(currentPageId);
  52. if (!currentPage || !topItem || !(sourceModel.layout && sourceModel.layout.style || sourceModel.style)) {
  53. return;
  54. }
  55. addOptions.insertBefore = null;
  56. addOptions.parentId = topItem.id;
  57. var properties = addOptions.layoutProperties;
  58. if (!properties.style) {
  59. properties.style = {};
  60. }
  61. // Convert top, left, width and height to pixel values so that they can be used in the out of bounds calculations
  62. var sourceStyle = sourceModel.layout && sourceModel.layout.style ? sourceModel.layout.style : sourceModel.style;
  63. var startingTop = sourceStyle.top;
  64. var startingLeft = sourceStyle.left;
  65. if (LayoutHelper._isOffsetAdjustmentRequired(currentPageId, topLayoutModel, dashboardApi)) {
  66. // Start from the source widget location, increment top and left offset in a loop until you find a location
  67. // with no widget. Then place that widget there. Set to a maximum of 30 so there is no possiblility of an inifnite lo
  68. var styles = topItem.items.map(function (x) {
  69. return x.style;
  70. });
  71. var counter = 0,
  72. newStartingTop = 0,
  73. newStartingLeft = 0;
  74. while (counter < 30) {
  75. var array = styles.filter(function (style) {
  76. return style.top === startingTop && style.left === startingLeft;
  77. });
  78. if (array && array.length === 0) {
  79. break;
  80. }
  81. newStartingTop = LayoutHelper._incrementStyleValue(startingTop);
  82. newStartingLeft = LayoutHelper._incrementStyleValue(startingLeft);
  83. if (LayoutHelper._isOutOfBounds(newStartingTop, sourceStyle.height, currentPage.clientHeight) === true || LayoutHelper._isOutOfBounds(newStartingLeft, sourceStyle.width, currentPage.clientWidth) === true) {
  84. break;
  85. }
  86. startingTop = newStartingTop;
  87. startingLeft = newStartingLeft;
  88. counter++;
  89. }
  90. }
  91. properties.style.top = startingTop;
  92. properties.style.left = startingLeft;
  93. properties.style.width = sourceStyle.width;
  94. properties.style.height = sourceStyle.height;
  95. };
  96. // Check if the current page has any existed widgets or registered content types
  97. // @currentPageId current page Id
  98. // @return true if currentPage has any widget or registered content types
  99. // false otherwise
  100. LayoutHelper._isOffsetAdjustmentRequired = function _isOffsetAdjustmentRequired(currentPageId, topLayoutModel, dashboardApi) {
  101. var currentPage = topLayoutModel.findModel(currentPageId);
  102. var types = ['widget'];
  103. var contentTypeRegistry = dashboardApi.getFeature('ContentTypeRegistry');
  104. types = types.concat(contentTypeRegistry.getRegisteredTypes());
  105. var layouts = currentPage.findDescendantsWithType(types);
  106. if (layouts.length > 0) {
  107. return true;
  108. } else {
  109. return false;
  110. }
  111. };
  112. LayoutHelper._incrementStyleValue = function _incrementStyleValue(value) {
  113. var val = value.match(/[0-9\\.]+/g),
  114. unit = value.match(/[^0-9\\.]+/g);
  115. if (val == null || unit == null || val.isEmpty || unit.isEmpty) {
  116. return value;
  117. }
  118. var newValue = parseFloat(val[0]);
  119. if (unit[0] === '%') {
  120. newValue = newValue + 5;
  121. } else {
  122. newValue = newValue + 25;
  123. }
  124. return newValue + unit[0];
  125. };
  126. /**
  127. * Given an origin and a dimension (height or width), calculates if the origin+dimension is out of bounds (above max)
  128. */
  129. LayoutHelper._isOutOfBounds = function _isOutOfBounds(origin, dimension, max) {
  130. var newOrigin = LayoutHelper._getPixelValue(origin, max);
  131. var newDimension = LayoutHelper._getPixelValue(dimension, max);
  132. return newOrigin > max - newDimension;
  133. };
  134. LayoutHelper._getPixelValue = function _getPixelValue(value, maxValue) {
  135. if (value[value.length - 1] === '%') {
  136. return parseFloat(value) / 100 * parseFloat(maxValue);
  137. } else {
  138. return parseFloat(value);
  139. }
  140. };
  141. /**
  142. * Return the model id for a given node id
  143. * @param nodeId
  144. * @returns
  145. */
  146. LayoutHelper.nodeIdToModelId = function nodeIdToModelId(nodeId) {
  147. return nodeId;
  148. };
  149. /**
  150. * Return the node id for a given model id
  151. * @param modelId
  152. * @returns
  153. */
  154. LayoutHelper.modelIdToNodeId = function modelIdToNodeId(modelId) {
  155. return modelId;
  156. };
  157. /**
  158. * Find the last selected page to insert. If not found, find the last visible page. For example, if you have this hierarchy of pages: Block/Tab/Absolute.
  159. * It will return the Absolute of the selected tab.
  160. * @param id of topLayoutModel
  161. */
  162. LayoutHelper.getLayoutContentContainer = function getLayoutContentContainer(topLayoutModelId, id, dashboardApi) {
  163. var findDropPage = function findDropPage(nTopPage) {
  164. var isMultiTab = false;
  165. var nSelectedPage = $('.pageTabContent .page:not(.pagegroup):not(.pagetemplateDropZone):not(.pagetemplateIndicator)', $(nTopPage)).filter(function (i, page) {
  166. isMultiTab = true;
  167. if (id) {
  168. return $('#' + id, page).length > 0;
  169. }
  170. // filter out the pages whose parent container is selected
  171. return $(this).closest('.pageTabContent').hasClass('selected');
  172. }).last();
  173. if (!isMultiTab) {
  174. var nLastPage = $('.page:not(.pagegroup):not(.pagetemplateDropZone):not(.pagetemplateIndicator)', $(nTopPage)).filter(function (i, page) {
  175. isMultiTab = true;
  176. if (id) {
  177. return $('#' + id, page).length > 0;
  178. }
  179. // filter out the hidden pages
  180. return $(this).is(':visible');
  181. }).last();
  182. return nLastPage && nLastPage[0] || nTopPage;
  183. } else {
  184. if (nSelectedPage && nSelectedPage[0]) {
  185. return nSelectedPage[0];
  186. } else if (dashboardApi) {
  187. var stringResources = dashboardApi.getDashboardCoreSvc('.StringResources');
  188. var translation = stringResources.get('pageSelectWarning');
  189. dashboardApi.showToast(translation, { type: 'info' });
  190. }
  191. }
  192. };
  193. var nPage = findDropPage(document.getElementById(topLayoutModelId));
  194. return nPage;
  195. };
  196. /**
  197. * Find the genericPage for the dropTarget, and if found, return the dropTargetNode as the content container node.
  198. * If not found, find the last visible page. It will return the Absolute of this page as the content container node.
  199. * @param {node} dropTargetNode
  200. * @param {modelObject} topLayoutModel
  201. * @param {apiInstance} dashboardApi
  202. */
  203. LayoutHelper.getLayoutContentContainerForDropTarget = function getLayoutContentContainerForDropTarget(dropTargetNode, topLayoutModel, dashboardApi) {
  204. if (dropTargetNode) {
  205. var layoutModelId = $(dropTargetNode).attr('id');
  206. var layoutModel = topLayoutModel && topLayoutModel.findModel(layoutModelId);
  207. if (layoutModel && layoutModel.type === 'genericPage') {
  208. return dropTargetNode;
  209. }
  210. }
  211. return LayoutHelper.getLayoutContentContainer(topLayoutModel.id, null, dashboardApi);
  212. };
  213. /**
  214. * Find the last selected page to insert. If not found, find the last visible dropzone. For example, if you have this hierarchy of pages: Block/Tab/Relative/templateDropZone.
  215. * It will return the the templateDropZone of the selected tab that the widget is apart of.
  216. * @param id of topLayoutModel
  217. */
  218. LayoutHelper.getLastVisiblePage = function getLastVisiblePage(topLayoutModelId, id, dashboardApi) {
  219. var nPage = LayoutHelper.getLayoutContentContainer(topLayoutModelId, id, dashboardApi);
  220. // Now check if we have an empty drop zone. If yes, we will use it
  221. var emptyDropZones = $(nPage).find('.pagetemplateDropZone.empty');
  222. return emptyDropZones.length > 0 ? emptyDropZones[0] : nPage;
  223. };
  224. /**
  225. * @returns container Page id containing the layout id
  226. * @param topLayoutModel
  227. */
  228. LayoutHelper.getContainerPageId = function getContainerPageId(topLayoutModel, id) {
  229. var topParent = topLayoutModel.findTopLevelParentItem(id);
  230. if (topParent) {
  231. return topParent.id;
  232. }
  233. return undefined;
  234. };
  235. /**
  236. * Calculates the minimum bounding page size to contain its widgets on absolute layout pages
  237. * @param layoutModel The layout model to start calculating the minimum bounding page size
  238. * @param minimumPageSize The minimum page size (optional)
  239. * @returns (object { width: xxx, height: xxx }) The smallest page size (pixels) that can contain all widgets in the given model
  240. */
  241. LayoutHelper.getBoundingPageSize = function getBoundingPageSize(layoutModel, minimumPageSize) {
  242. if (!layoutModel) {
  243. return undefined;
  244. }
  245. var boundingPageSize = { width: 0, height: 0 };
  246. var pageModels = void 0;
  247. // Get the starting point pages
  248. if (layoutModel.type === 'genericPage') {
  249. pageModels = [layoutModel];
  250. } else {
  251. pageModels = layoutModel.findDescendantsWithType('genericPage');
  252. }
  253. if (minimumPageSize) {
  254. boundingPageSize = _.deepClone(minimumPageSize);
  255. }
  256. for (var _iterator = pageModels, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
  257. var _ref;
  258. if (_isArray) {
  259. if (_i >= _iterator.length) break;
  260. _ref = _iterator[_i++];
  261. } else {
  262. _i = _iterator.next();
  263. if (_i.done) break;
  264. _ref = _i.value;
  265. }
  266. var pageModel = _ref;
  267. if (pageModel.layoutPositioning === 'absolute') {
  268. var itemLayoutModels = pageModel.findDescendantsWithType(['widget', 'group']);
  269. for (var _iterator2 = itemLayoutModels, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
  270. var _ref2;
  271. if (_isArray2) {
  272. if (_i2 >= _iterator2.length) break;
  273. _ref2 = _iterator2[_i2++];
  274. } else {
  275. _i2 = _iterator2.next();
  276. if (_i2.done) break;
  277. _ref2 = _i2.value;
  278. }
  279. var itemLayoutModel = _ref2;
  280. var rawStyle = LayoutHelper._getRawStyle(itemLayoutModel.style, 'px');
  281. if (rawStyle) {
  282. var right = rawStyle.left + rawStyle.width;
  283. if (right > boundingPageSize.width) {
  284. boundingPageSize.width = right;
  285. }
  286. var bottom = rawStyle.top + rawStyle.height;
  287. if (bottom > boundingPageSize.height) {
  288. boundingPageSize.height = bottom;
  289. }
  290. }
  291. }
  292. }
  293. }
  294. return boundingPageSize && boundingPageSize.width ? boundingPageSize : undefined;
  295. };
  296. LayoutHelper._getRawStyle = function _getRawStyle(style, units) {
  297. // Validate style units
  298. if (style.left.indexOf(units) !== -1 && style.top.indexOf(units) !== -1 && style.width.indexOf(units) !== -1 && style.height.indexOf(units) !== -1) {
  299. return {
  300. left: parseInt(style.left, 10),
  301. top: parseInt(style.top, 10),
  302. width: parseInt(style.width, 10),
  303. height: parseInt(style.height, 10)
  304. };
  305. }
  306. return undefined;
  307. };
  308. LayoutHelper.styleIntToPx = function styleIntToPx(style) {
  309. style.top = style.top + 'px';
  310. style.left = style.left + 'px';
  311. style.width = style.width + 'px';
  312. style.height = style.height + 'px';
  313. };
  314. return LayoutHelper;
  315. }();
  316. return LayoutHelper;
  317. });
  318. //# sourceMappingURL=LayoutHelper.js.map