Absolute.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  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(['./LayoutBaseView', 'jquery', 'underscore', '../../../../lib/@waca/core-client/js/core-client/utils/Deferred', '../../LayoutHelper', '../../../util/PxPercentUtil', '../../../glass/util/InstrumentationUtil'], function (BaseLayout, $, _, Deferred, LayoutHelper, PxPercentUtil, InstrumentationUtil) {
  8. var PageLayout = null;
  9. var WIDGET_PADDING = 5;
  10. PageLayout = BaseLayout.extend({
  11. init: function init(options) {
  12. PageLayout.inherited('init', this, arguments);
  13. if (this.layoutController.topLayoutModel) {
  14. this.layoutController.topLayoutModel.on('change:showGrid', this.renderGrid, this);
  15. this.layoutController.topLayoutModel.on('change:fitPage', this.renderGrid, this);
  16. this.layoutController.topLayoutModel.on('change:pageSize', this.onPageSize, this);
  17. }
  18. this.services = options.services;
  19. this._templateHelper = options.templateHelper; // Optional
  20. this.whenIsReadyDfd = new Deferred();
  21. this.specializeConsumeView(['setPreferredLocation']);
  22. this._initialize();
  23. },
  24. createDropZone: function createDropZone() {
  25. var _this = this;
  26. this._dropTarget = this._dndManager.addDropTarget(this.domNode, {
  27. accepts: this.accepts.bind(this),
  28. onDrop: this.onDrop.bind(this),
  29. onDragLeave: this.onDragLeave.bind(this),
  30. onDragEnd: this.onDragEnd.bind(this),
  31. onDragMove: this.onDragMove.bind(this),
  32. priority: -100 /* Indicate that any other drop zone takes priority */
  33. , info: function info(domNode, event) {
  34. return _this._getCoordsRelativeToTarget(domNode, event);
  35. },
  36. type: 'dropOnCanvas'
  37. });
  38. this._dropHandler = this._dndManager.registerDropHandler({
  39. accepts: function accepts(dragPayload, dropPayload) {
  40. return dragPayload.type.match(/content\..*/) && dropPayload.type === 'dropOnCanvas';
  41. },
  42. onDrop: function onDrop(dragPayload, dropPayload) {
  43. _this.onContentDrop(dragPayload, dropPayload);
  44. }
  45. });
  46. },
  47. _getCoordsRelativeToTarget: function _getCoordsRelativeToTarget(domNode, event) {
  48. var offset = $(domNode).offset();
  49. return {
  50. top: Math.round(event.pageY - offset.top + domNode.scrollTop),
  51. left: Math.round(event.pageX - offset.left + domNode.scrollLeft)
  52. };
  53. },
  54. onContentDrop: function onContentDrop(dragPayload, dropPayload) {
  55. var content = JSON.parse(JSON.stringify(dragPayload.data));
  56. var specStyle = content.spec.style || {};
  57. if (dropPayload && dropPayload.info) {
  58. var style = {
  59. width: specStyle.width,
  60. height: specStyle.height,
  61. top: dropPayload.info.top,
  62. left: dropPayload.info.left
  63. };
  64. this._stylePxToInt(style);
  65. this._setStyleWithinBounds(style);
  66. LayoutHelper.styleIntToPx(style);
  67. specStyle.width = style.width;
  68. specStyle.height = style.height;
  69. specStyle.left = style.left;
  70. specStyle.top = style.top;
  71. content.spec.style = specStyle;
  72. var canvas = this.dashboardApi.getFeature('Canvas');
  73. return canvas.addContent(content);
  74. }
  75. this.clearDragState();
  76. },
  77. onShow: function onShow() {
  78. PageLayout.inherited('onShow', this, arguments);
  79. this.renderGrid();
  80. },
  81. whenIsReady: function whenIsReady() {
  82. return this.whenIsReadyDfd.promise;
  83. },
  84. destroy: function destroy() {
  85. if (this._dropTarget) {
  86. this._dropTarget.remove();
  87. this._dropTarget = null;
  88. }
  89. if (this._dropHandler) {
  90. this._dropHandler.deregister();
  91. this._dropHandler = null;
  92. }
  93. this._dndManager = null;
  94. if ($.glassdnd && $.glassdnd.cancelDroppable) {
  95. $.glassdnd.cancelDroppable(this.$el);
  96. }
  97. this.$el.find('.absoluteLayoutGrid').remove();
  98. this.layoutController.topLayoutModel.off('change:showGrid', this.renderGrid, this);
  99. this.layoutController.topLayoutModel.off('change:pageSize', this.onPageSize, this);
  100. PageLayout.inherited('destroy', this, arguments);
  101. },
  102. removeChild: function removeChild() {
  103. PageLayout.inherited('removeChild', this, arguments);
  104. },
  105. _initialize: function _initialize() {
  106. this._dndManager = this.dashboardApi.getFeature('DashboardDnd.internal');
  107. this.whenIsReadyDfd.resolve();
  108. this.createDropZone();
  109. },
  110. _getDragPositionRelativeToTarget: function _getDragPositionRelativeToTarget(dragObject, target) {
  111. var offset = $(target).offset();
  112. return {
  113. top: Math.round(dragObject.position.y - offset.top + target.scrollTop),
  114. left: Math.round(dragObject.position.x - offset.left + target.scrollLeft)
  115. };
  116. },
  117. _getDragBoxForNewDrop: function _getDragBoxForNewDrop(dragObject, target) {
  118. var size = this._getDragObjectDimensions(dragObject);
  119. var coords = this._getDragPositionRelativeToTarget(dragObject, target);
  120. var dragBox = {
  121. top: coords.top,
  122. left: coords.left,
  123. ids: []
  124. };
  125. dragBox.right = dragBox.left + parseInt(size.width, 10);
  126. dragBox.bottom = dragBox.top + parseInt(size.height, 10);
  127. dragBox.center_x = (dragBox.left + dragBox.right) / 2;
  128. dragBox.center_y = (dragBox.top + dragBox.bottom) / 2;
  129. return dragBox;
  130. },
  131. /**
  132. * Get the drag object dimensions. We use the layout/style information if avaliable. If not, we use the drag avatar.
  133. */
  134. _getDragObjectDimensions: function _getDragObjectDimensions(dragObject) {
  135. var style = dragObject.data.layoutProperties && dragObject.data.layoutProperties.style || {};
  136. var $avatar = $(dragObject.avatar);
  137. if ($avatar.length === 0) {
  138. $avatar = $('.avatar');
  139. }
  140. return {
  141. width: style.width || $avatar.width() || '10px', //If we can't find anything to give a size, default to a 10*10 box
  142. height: style.height || $avatar.height() || '10px'
  143. };
  144. },
  145. _getDragBox: function _getDragBox(nodeInfoList) {
  146. var dragBox = nodeInfoList.reduce(function (box, current) {
  147. var $n = $(current.node),
  148. top,
  149. left,
  150. bottom,
  151. right;
  152. if (current.dropPosition) {
  153. top = current.dropPosition.y;
  154. left = current.dropPosition.x;
  155. bottom = top + $n.outerHeight();
  156. right = left + $n.outerWidth();
  157. } else {
  158. //If no dropPosition is present, don't affect the dragBox. This happens for group members, for which we can't currently scroll the canvas without disconnecting the widget and mouse.
  159. top = Infinity;
  160. left = Infinity;
  161. bottom = 0;
  162. right = 0;
  163. }
  164. box.top = Math.min(box.top, top);
  165. box.left = Math.min(box.left, left);
  166. box.bottom = Math.max(box.bottom, bottom);
  167. box.right = Math.max(box.right, right);
  168. box.ids.push(current.node.id);
  169. return box;
  170. }, {
  171. top: Infinity,
  172. left: Infinity,
  173. bottom: 0,
  174. right: 0,
  175. ids: []
  176. });
  177. dragBox.center_x = (dragBox.left + dragBox.right) / 2;
  178. dragBox.center_y = (dragBox.top + dragBox.bottom) / 2;
  179. return dragBox;
  180. },
  181. /**
  182. * Called by the DnD manager to check if this drop zone accept the dragged object
  183. * We only accept object with type widget, pin and groupContent
  184. * @returns {Boolean}
  185. */
  186. accepts: function accepts(dragObject) {
  187. var canvasDnD = this.dashboardApi.getFeature('CanvasDnD');
  188. return canvasDnD.accepts(dragObject, {
  189. fromCanvas: true
  190. });
  191. },
  192. /**
  193. * Called by the DnD manager when we move inside the drop zone (only if the drop zone accepts the object)
  194. * @param dragBox {object} A drag-position object with left,right,top,bottom and center_x, center_y.
  195. *
  196. */
  197. onDragMove: function onDragMove(dragObject, target) {
  198. this.$el.addClass('layoutDragInProgress');
  199. var nodeInfoList = dragObject.data && dragObject.data.nodeInfoList;
  200. if (nodeInfoList && nodeInfoList.length) {
  201. dragObject.dragBox = this._getDragBox(nodeInfoList);
  202. } else {
  203. dragObject.dragBox = this._getDragBoxForNewDrop(dragObject, target);
  204. }
  205. this.adjustScrollPosition(dragObject);
  206. },
  207. _moveDrop: function _moveDrop(dragObject) {
  208. var positionUpdateArray = [],
  209. nodeInfo,
  210. positionInfo;
  211. var i = 0;
  212. for (var iLen = dragObject.data.nodeInfoList.length; i < iLen; i++) {
  213. nodeInfo = dragObject.data.nodeInfoList[i];
  214. positionInfo = this.getWidgetPositionUpdate(nodeInfo);
  215. if (positionInfo) {
  216. positionUpdateArray.push(positionInfo);
  217. }
  218. }
  219. var transactionApi = this.dashboardApi.getFeature('Transaction');
  220. var transactionToken = transactionApi.startTransaction();
  221. var nodeIds = positionUpdateArray.map(function (update) {
  222. return update.id;
  223. });
  224. var insertBeforeMap = {};
  225. _.reduce(positionUpdateArray, function (oMap, item) {
  226. if (item.insertBefore) {
  227. oMap[item.id] = item.insertBefore;
  228. }
  229. return oMap;
  230. }, insertBeforeMap);
  231. this.dashboardApi.getCanvas().moveContent(this.model.id, nodeIds, transactionToken, insertBeforeMap);
  232. this.updateModel(positionUpdateArray, transactionToken);
  233. transactionApi.endTransaction(transactionToken);
  234. },
  235. _newDrop: function _newDrop(dragObject, target, isTrackAction) {
  236. var _this2 = this;
  237. var layoutProperties = dragObject.data.layoutProperties || {};
  238. var offset = $(target).offset();
  239. if (target && offset) {
  240. return this._getModelToAddFromDragObject(dragObject).then(function (newModel) {
  241. if (newModel) {
  242. if (!layoutProperties.style) {
  243. layoutProperties.style = {};
  244. }
  245. var coords = _this2._getDragPositionRelativeToTarget(dragObject, target);
  246. var style = {
  247. width: layoutProperties.style.width,
  248. height: layoutProperties.style.height,
  249. top: coords.top,
  250. left: coords.left
  251. };
  252. _this2._stylePxToInt(style);
  253. _this2._setStyleWithinBounds(style);
  254. LayoutHelper.styleIntToPx(style);
  255. layoutProperties.style.width = style.width;
  256. layoutProperties.style.height = style.height;
  257. layoutProperties.style.left = style.left;
  258. layoutProperties.style.top = style.top;
  259. var widgetSpec = {
  260. parentId: _this2.id,
  261. model: newModel,
  262. layoutProperties: layoutProperties
  263. };
  264. if (isTrackAction) {
  265. InstrumentationUtil.trackWidget('created', _this2.dashboardApi, widgetSpec.model);
  266. }
  267. _this2._addWidget(widgetSpec, dragObject.isTouch);
  268. }
  269. });
  270. } else {
  271. return Promise.resolve();
  272. }
  273. },
  274. /**
  275. * A helper function handles pin dropping.
  276. * @param {object} dragObject - The dragObject to process
  277. */
  278. _onPinDrop: function _onPinDrop(dragObject) {
  279. var pinSpec = dragObject.data.pinSpec;
  280. var isTouch = dragObject.isTouch;
  281. var x = dragObject.position.x;
  282. var y = dragObject.position.y;
  283. // pin drop absolute location
  284. var top = Math.round(y - this.$el.offset().top) + 'px';
  285. var left = Math.round(x - this.$el.offset().left) + 'px';
  286. // we only need to consider gemini widget
  287. if (pinSpec.contentType === 'boardFragment') {
  288. // set absolute drop position back into the layout for top-left corner.
  289. // height width retained from original widget, this could be absolute
  290. // or a percentage if it came from a template. %age retains original
  291. // dimensions still
  292. if (!pinSpec.content.layout.style) {
  293. pinSpec.content.layout.style = {};
  294. }
  295. pinSpec.content.layout.style.top = top;
  296. pinSpec.content.layout.style.left = left;
  297. // If our pinSpec is too big for the content box, shrink and center it to fit. Only do this for pan-and-zoom.
  298. this._resizeToFitBoundaries(this.consumeView.getMaximumHeight(), this.consumeView.getMaximumWidth(), pinSpec.content.layout.style);
  299. }
  300. pinSpec.parentId = this.id;
  301. this._processWidgetSpecForPin(pinSpec, isTouch);
  302. },
  303. /** Resize the top, left, width & height of style such that they fit within the specified maxHeight and maxWidth,
  304. * maintaining their original aspect ratio.
  305. * @param maxHeight - maximum height of the layout specified in style
  306. * @param maxWidth - maximum width of the layout specified in style
  307. * @param style - expected to have height and width properties to compare with maxHeight and maxWidth. Will be resized to fit.
  308. */
  309. _resizeToFitBoundaries: function _resizeToFitBoundaries(maxHeight, maxWidth, style) {
  310. var width = parseInt(style.width, 10);
  311. var dWidth = maxWidth / width;
  312. var height = parseInt(style.height, 10);
  313. var dHeight = maxHeight / height;
  314. if (dWidth < 1 || dHeight < 1) {
  315. var dMultiple = Math.min(dWidth, dHeight);
  316. style.height = Math.round(height * dMultiple) + 'px';
  317. style.width = Math.round(width * dMultiple) + 'px';
  318. style.left = Math.round((maxWidth - Math.round(width * dMultiple)) / 2) + 'px';
  319. style.top = Math.round((maxHeight - Math.round(height * dMultiple)) / 2) + 'px';
  320. }
  321. },
  322. /**
  323. * Called by the drag&drop manager when a drop happens
  324. *
  325. * @param dragObject
  326. * @param targetNode
  327. */
  328. onDrop: function onDrop(dragObject, targetNode) {
  329. var _this3 = this;
  330. var promise = void 0;
  331. if (!dragObject.data && !targetNode) {
  332. promise = this._newDrop(dragObject.originalEvent, dragObject.targetNode);
  333. } else if (dragObject.data.operation === 'move') {
  334. promise = Promise.resolve(this._moveDrop(dragObject));
  335. } else if (dragObject.type === 'pin' && dragObject.data.operation === 'new') {
  336. //calls _onPinDrop when the dragObject with type pin
  337. promise = Promise.resolve(this._onPinDrop(dragObject));
  338. } else {
  339. promise = this._newDrop(dragObject, targetNode, /*isTrackAction*/true);
  340. }
  341. return promise.then(function () {
  342. return _this3.clearDragState();
  343. });
  344. },
  345. onDragLeave: function onDragLeave() {
  346. this.clearDragState(true);
  347. },
  348. onDragEnd: function onDragEnd() {
  349. this.clearDragState();
  350. this.previousDragBox = null;
  351. },
  352. clearDragState: function clearDragState() {
  353. this.$el.removeClass('layoutDragInProgress');
  354. },
  355. _changePercentModelToPixel: function _changePercentModelToPixel(style) {
  356. var $referenceView = this.$el;
  357. // this view could be hidden, so find a view that isn't hidden.
  358. if (!$referenceView.is(':visible')) {
  359. $referenceView = $(this.layoutController.getLastVisiblePage());
  360. }
  361. var referenceViewSize = {
  362. width: $referenceView.width(),
  363. height: $referenceView.height()
  364. };
  365. PxPercentUtil.changePercentPropertiesToPixel(style, referenceViewSize);
  366. },
  367. /**
  368. * Set the preferred layout location in the layout properties
  369. *
  370. * @param options
  371. * options.layoutProperties
  372. * options.parentId
  373. */
  374. setPreferredLocation: function setPreferredLocation(options) {
  375. var locationSet = false;
  376. if (this._templateHelper && !options.layoutProperties.style) {
  377. locationSet = this._templateHelper.setPreferredLocation(options);
  378. }
  379. if (!locationSet) {
  380. var properties = options.layoutProperties;
  381. if (!properties.style) {
  382. properties.style = {};
  383. }
  384. // get the dimensions of the widget being added
  385. var size = {
  386. width: properties.style.width || '300',
  387. height: properties.style.height || '300'
  388. };
  389. if (size.width.indexOf('%') !== -1 || size.height.indexOf('%') !== -1) {
  390. this._changePercentModelToPixel(size);
  391. }
  392. var width = parseInt(size.width, 10);
  393. var height = parseInt(size.height, 10);
  394. var location = !properties.style.top && !properties.style.left ? this.findAvailableSpace({
  395. width: width,
  396. height: height
  397. }) : null;
  398. if (location) {
  399. properties.style.top = location.top + 'px';
  400. properties.style.left = location.left + 'px';
  401. properties.style.width = properties.style.width || (location.width || width) + 'px';
  402. properties.style.height = properties.style.height || (location.height || height) + 'px';
  403. }
  404. // update the parent id
  405. options.parentId = this.model.id;
  406. }
  407. },
  408. getWidgetContainerNode: function getWidgetContainerNode() {
  409. return this.domNode;
  410. },
  411. /**
  412. * Find the location of the available space that fits the given dimensions
  413. *
  414. * 1- Sort the list of existing rectangles from left to right and from top to bottom using the center of the box.
  415. * 2- for each rectangle in the list do the following:
  416. * - find if there is available space that is adjacent to the bottom border
  417. *
  418. *
  419. * @param dimensions - dimensions that we are trying to find space for
  420. *
  421. * dimensions.width
  422. * dimensions.height
  423. *
  424. * @returns location - object that contains the top and left positions
  425. *
  426. */
  427. findAvailableSpace: function findAvailableSpace(dimensions) {
  428. // Get the scan options that will be used as input to scan for available space
  429. var usedSpaceRectArray = this.getChildrenRectList();
  430. // sort the rectangle from left to right and from top to bottom ( using the bottom border as the reference point).
  431. // This is the order that we use to try to find an available position
  432. var sortedUsedSpace = usedSpaceRectArray.slice(0).sort(function (a, b) {
  433. return a.height - b.height === 0 ? a.top - b.top === 0 ? a.left - b.left : a.top - b.top : a.height - b.height;
  434. });
  435. var i, iLen, rect;
  436. var coords = {
  437. top: 0,
  438. left: 0
  439. };
  440. for (i = 0, iLen = sortedUsedSpace.length; i < iLen; i++) {
  441. rect = sortedUsedSpace[i];
  442. coords = this.findSpaceAdjacentToBottomBorder(rect, dimensions, usedSpaceRectArray);
  443. if (coords) {
  444. break;
  445. }
  446. }
  447. var style = {
  448. width: dimensions.width,
  449. height: dimensions.height,
  450. top: coords.top,
  451. left: coords.left
  452. };
  453. this._setStyleWithinBounds(style);
  454. coords.top = style.top;
  455. coords.left = style.left;
  456. coords.width = style.width;
  457. coords.height = style.height;
  458. return coords;
  459. },
  460. /**
  461. * Check that widget style does not fall outside max bounds.
  462. * Updates the style to fit within bounds
  463. * Style should be unitless
  464. *
  465. * @param style
  466. */
  467. _setStyleWithinBounds: function _setStyleWithinBounds(style) {
  468. // will return Infinity if scrollDrop is supported
  469. var maxWidth = this.consumeView.getMaximumWidth();
  470. var maxHeight = this.consumeView.getMaximumHeight();
  471. if (isFinite(maxHeight) && isFinite(maxWidth)) {
  472. // widgets that are larger than layout will be fit to max size, maintaining ratio
  473. this._resizeToFitBoundaries(maxHeight, maxWidth, style);
  474. // convert to integer values for rest of operations
  475. this._stylePxToInt(style);
  476. // widgets with unknown width & height will occupy relative % of layout size
  477. if (!style.width && !style.height) {
  478. if (maxHeight > maxWidth) {
  479. style.width = Math.floor(maxWidth * 0.5);
  480. style.height = Math.floor(style.width * 3 / 4);
  481. } else {
  482. style.height = Math.floor(maxHeight * 0.5);
  483. style.width = Math.floor(style.height * 4 / 3);
  484. }
  485. }
  486. this.moveToFitBoundaries(maxHeight, maxWidth, style);
  487. }
  488. },
  489. _stylePxToInt: function _stylePxToInt(style) {
  490. for (var key in style) {
  491. if (style.hasOwnProperty(key)) {
  492. var value = style[key];
  493. if (value) {
  494. style[key] = parseInt(value, 10);
  495. }
  496. }
  497. }
  498. },
  499. /**
  500. * Find a space that is adjacent to the bottom border of a given rectangle where the space is bigger or equal to the given dimensions
  501. *
  502. *
  503. * @param topRect
  504. * @param dimensions
  505. * @param rectList
  506. * @returns
  507. */
  508. findSpaceAdjacentToBottomBorder: function findSpaceAdjacentToBottomBorder(topRect, dimensions, rectList) {
  509. // Find all the rectangles below the current rectangle that might intersect with the rectangle we are trying to add.
  510. var list = _.filter(rectList, function (rect) {
  511. return rect !== topRect && topRect.top + topRect.height < rect.top + rect.height && topRect.top + topRect.height + dimensions.height + WIDGET_PADDING > rect.top && rect.left <= topRect.left + topRect.width + dimensions.width + WIDGET_PADDING && rect.left + rect.width >= topRect.left - dimensions.width;
  512. });
  513. var coords;
  514. if (list.length === 0) {
  515. coords = {
  516. top: topRect.top + topRect.height + WIDGET_PADDING,
  517. left: topRect.left
  518. };
  519. } else {
  520. coords = this.findSpaceBetweenRectangles(topRect, list, dimensions);
  521. }
  522. return coords;
  523. },
  524. /**
  525. * Find a space that fits the given dimension by scanning between the rectangles in the list
  526. *
  527. * @param topRect - top boundary rectangle
  528. * @param list - list if rectangle to scan
  529. * @param dimensions - dimensions of the new rectangle we are trying to find
  530. * @param padding - padding used between rectangles
  531. * @returns
  532. */
  533. findSpaceBetweenRectangles: function findSpaceBetweenRectangles(topRect, list, dimensions) {
  534. var width = dimensions.width + WIDGET_PADDING;
  535. list.sort(function (a, b) {
  536. return a.left - b.left;
  537. });
  538. // If there is space before the first rectangle, then we insert before
  539. var top = topRect.top + topRect.height + WIDGET_PADDING;
  540. var first = list[0];
  541. if (first.left > topRect.left && first.left >= width && first.left - width > topRect.left - width) {
  542. return {
  543. top: top,
  544. left: Math.min(topRect.left, first.left - width)
  545. };
  546. }
  547. // Scan if there is space after each rectangle.
  548. var maxWidth = $(this.getWidgetContainerNode()).width();
  549. var rect, next;
  550. for (var i = 0; i < list.length; i++) {
  551. rect = list[i];
  552. next = list[i + 1];
  553. if (next && rect.left + rect.width > next.left + next.width) {
  554. // the next rectangle is contained within the current one. Throw it away and replace with the current rectangle.
  555. list[i + 1] = rect;
  556. } else {
  557. var rectLeft = next ? next.left : maxWidth;
  558. if (rect.left + rect.width + width <= rectLeft && topRect.left + topRect.width + width > rect.left + rect.width + width) {
  559. return {
  560. top: top,
  561. left: rect.left + rect.width + WIDGET_PADDING
  562. };
  563. }
  564. }
  565. }
  566. return null;
  567. },
  568. /**
  569. * Get the list of rectangle information for every child of this layout
  570. *
  571. * @returns {Array}
  572. */
  573. getChildrenRectList: function getChildrenRectList() {
  574. var usedSpaceArray = [];
  575. // Virtual boxes at the top and left of the page to force adding widgets at a certain padding
  576. usedSpaceArray.push({
  577. top: 0,
  578. left: 0,
  579. height: this.getMinimumTop() - WIDGET_PADDING,
  580. width: Math.round($(this.getWidgetContainerNode()).width())
  581. });
  582. usedSpaceArray.push({
  583. top: 0,
  584. left: 0,
  585. height: Math.round($(this.getWidgetContainerNode()).height()),
  586. width: this.getMinimumLeft() - WIDGET_PADDING
  587. });
  588. var children = $(this.getWidgetContainerNode()).children().not('.relativeLayoutGrid, .absoluteLayoutGrid, .pagetemplateIndicator, .pagetemplateDropZone, .sizeDashboardIndicatorContainer');
  589. var i, iLen;
  590. for (i = 0, iLen = children.length; i < iLen; i++) {
  591. var $child = $(children[i]);
  592. usedSpaceArray.push({
  593. top: $child[0].offsetTop,
  594. left: $child[0].offsetLeft,
  595. height: $child.outerHeight(),
  596. width: $child.outerWidth()
  597. });
  598. }
  599. return usedSpaceArray;
  600. },
  601. adjustScrollPosition: function adjustScrollPosition(dragObject) {
  602. var dragBox = dragObject.dragBox;
  603. var scrollInfo = this.getScrollInfo(dragObject);
  604. this.scrollToDragBox(dragBox, scrollInfo);
  605. },
  606. getScrollInfo: function getScrollInfo(dragObject) {
  607. var dragBox = dragObject.dragBox;
  608. var $node = this.getScrollAreaNode();
  609. var scrollInfo = {
  610. h: $node.height(),
  611. w: $node.width(),
  612. scrollTop: $node.scrollTop(),
  613. scrollLeft: $node.scrollLeft(),
  614. $node: $node,
  615. direction: []
  616. };
  617. if (this.previousDragBox) {
  618. if (dragBox.left !== this.previousDragBox.left) {
  619. scrollInfo.direction[dragBox.left - this.previousDragBox.left > 0 ? 'right' : 'left'] = true;
  620. }
  621. if (dragBox.top !== this.previousDragBox.top) {
  622. scrollInfo.direction[dragBox.top - this.previousDragBox.top > 0 ? 'bottom' : 'top'] = true;
  623. }
  624. }
  625. this.previousDragBox = dragBox;
  626. return scrollInfo;
  627. },
  628. getScrollAreaNode: function getScrollAreaNode() {
  629. var $parents = this.$el.parents('.page');
  630. var $el = this.$el;
  631. var index = 0;
  632. while ($el && $el.length && $el.css('overflow') !== 'auto') {
  633. $el = $parents.eq(index);
  634. index++;
  635. }
  636. if ($el.length === 0) {
  637. $el = this.$el;
  638. }
  639. return $el;
  640. },
  641. scrollToDragBox: function scrollToDragBox(dragBox, scrollInfo) {
  642. var SCROLL_INCREMENT = 16;
  643. var DRAG_EDGE_BUFFER_SIZE = 2; //Size of a buffer to add to the top & left checks due to varying mouse position. This will only be called as long as the mouse is over the canvas, and when inserting a widget, the mouse cursor is just outside the top-left corner making checks for scrolling up and left problematic.
  644. if (dragBox.bottom > scrollInfo.scrollTop + scrollInfo.h && scrollInfo.direction.bottom) {
  645. scrollInfo.$node.scrollTop(scrollInfo.scrollTop + SCROLL_INCREMENT);
  646. } else if (scrollInfo.scrollTop > dragBox.top - DRAG_EDGE_BUFFER_SIZE && scrollInfo.direction.top) {
  647. scrollInfo.$node.scrollTop(scrollInfo.scrollTop - SCROLL_INCREMENT);
  648. }
  649. if (dragBox.right > scrollInfo.scrollLeft + scrollInfo.w && scrollInfo.direction.right) {
  650. scrollInfo.$node.scrollLeft(scrollInfo.scrollLeft + SCROLL_INCREMENT);
  651. } else if (scrollInfo.scrollLeft > dragBox.left - DRAG_EDGE_BUFFER_SIZE && scrollInfo.direction.left) {
  652. scrollInfo.$node.scrollLeft(scrollInfo.scrollLeft - SCROLL_INCREMENT);
  653. }
  654. },
  655. renderGrid: function renderGrid() {
  656. var showGridProp = this.model.getValueFromSelfOrParent('showGrid');
  657. var showGrid = showGridProp === undefined ? false : showGridProp;
  658. var layoutPositioning = this.model.getValueFromSelfOrParent('layoutPositioning');
  659. if (showGrid && layoutPositioning === 'absolute') {
  660. if (this.$el.hasClass('gridCapable')) {
  661. this.$el.find('.absoluteLayoutGrid').remove();
  662. this.$el.prepend('<div class=absoluteLayoutGrid></div>');
  663. }
  664. } else {
  665. this.$el.find('.absoluteLayoutGrid').remove();
  666. }
  667. },
  668. onResize: function onResize() {
  669. PageLayout.inherited('onResize', this, arguments);
  670. this.renderGrid();
  671. },
  672. onPageSize: function onPageSize() {
  673. this.renderGrid();
  674. }
  675. });
  676. return PageLayout;
  677. });
  678. //# sourceMappingURL=Absolute.js.map