Show:
  1. var getClassName = A.getClassName;
  2. /**
  3. * Widget extension, which can be used to suggest alignment points based on
  4. * position attribute to base Widget class, through the
  5. * [Base.build](Base.html#method_build) method. It also tries to find
  6. * the best position in case the widget doesn't fit it's constrainment node.
  7. *
  8. * @class A.WidgetPositionAlignSuggestion
  9. * @param {Object} The user configuration object
  10. */
  11. function PositionAlignSuggestion() {}
  12. /**
  13. * Static property used to define the default attribute
  14. * configuration.
  15. *
  16. * @property ATTRS
  17. * @type Object
  18. * @static
  19. */
  20. PositionAlignSuggestion.ATTRS = {
  21. /**
  22. * Determine the position of the tooltip.
  23. *
  24. * @attribute position
  25. * @default top
  26. * @type {String}
  27. */
  28. position: {
  29. getter: '_getPosition',
  30. validator: '_validatePosition',
  31. value: 'top'
  32. }
  33. };
  34. A.mix(PositionAlignSuggestion.prototype, {
  35. /**
  36. * Property defining the align points based on the suggested `position`.
  37. *
  38. * @property POSITION_ALIGN_SUGGESTION
  39. * @type {}
  40. */
  41. POSITION_ALIGN_SUGGESTION: {
  42. bottom: [A.WidgetPositionAlign.TC, A.WidgetPositionAlign.BC],
  43. left: [A.WidgetPositionAlign.RC, A.WidgetPositionAlign.LC],
  44. right: [A.WidgetPositionAlign.LC, A.WidgetPositionAlign.RC],
  45. top: [A.WidgetPositionAlign.BC, A.WidgetPositionAlign.TC]
  46. },
  47. _hasAlignmentPoints: false,
  48. _lastPosition: null,
  49. /**
  50. * Construction logic executed during WidgetPositionAlignSuggestion
  51. * instantiation. Lifecycle.
  52. *
  53. * @method initializer
  54. * @protected
  55. */
  56. initializer: function(config) {
  57. var instance = this;
  58. if (config && config.align && config.align.points) {
  59. instance._hasAlignmentPoints = true;
  60. instance._setPositionAccordingPoints();
  61. }
  62. A.on(instance._onUISetAlignPAS, instance, '_uiSetAlign');
  63. A.after(instance._afterRenderUIPAS, instance, 'renderUI');
  64. instance.after('positionChange', instance._afterPositionChangePAS);
  65. },
  66. /**
  67. * Suggest alignment for the node based on the `position` suggestion.
  68. *
  69. * @method suggestAlignment
  70. * @attribute alignNode
  71. */
  72. suggestAlignment: function(alignNode) {
  73. var instance = this,
  74. align;
  75. align = instance.get('align') || {};
  76. if (alignNode) {
  77. align.node = alignNode;
  78. }
  79. if (!instance._hasAlignmentPoints) {
  80. align.points = instance._getAlignPointsSuggestion(
  81. instance.get('position'));
  82. }
  83. instance.set('align', align);
  84. },
  85. /**
  86. * Fire after `boundingBox` position changes.
  87. *
  88. * @method _afterPositionChangePAS
  89. * @param event
  90. * @protected
  91. */
  92. _afterPositionChangePAS: function(event) {
  93. var instance = this;
  94. instance._uiSetPosition(event.newVal, event.prevVal);
  95. },
  96. /**
  97. * Fire after `renderUI` method.
  98. *
  99. * @method _afterRenderUIPAS
  100. * @param event
  101. * @protected
  102. */
  103. _afterRenderUIPAS: function() {
  104. var instance = this;
  105. instance._uiSetPosition(instance.get('position'));
  106. },
  107. /**
  108. * Returns true if the widget can fit inside it's constrainment node.
  109. *
  110. * @method _canWidgetAlignToNode
  111. * @param node
  112. * @param position
  113. * @protected
  114. */
  115. _canWidgetAlignToNode: function(node, position) {
  116. var instance = this,
  117. constrainedXY,
  118. points = instance._getAlignPointsSuggestion(position),
  119. xy = instance._getAlignedXY(node, points);
  120. constrainedXY = instance.getConstrainedXY(xy);
  121. return (constrainedXY[0] === xy[0] && constrainedXY[1] === xy[1]);
  122. },
  123. /**
  124. * Finds the position in which the widget fits without having to have its
  125. * coordinates changed due to its constrainment node.
  126. *
  127. * @method _findBestPosition
  128. * @param node
  129. * @protected
  130. */
  131. _findBestPosition: function(node) {
  132. var instance = this,
  133. position = instance.get('position'),
  134. testPositions = [position, 'top', 'bottom', 'right', 'left'],
  135. trigger = A.one(node);
  136. if (trigger && !trigger.inViewportRegion()) {
  137. return instance._findBestPositionOutsideViewport(trigger);
  138. } else {
  139. testPositions = A.Array.dedupe(testPositions);
  140. A.Array.some(testPositions, function(testPosition) {
  141. if (instance._canWidgetAlignToNode(trigger, testPosition)) {
  142. position = testPosition;
  143. return true;
  144. }
  145. });
  146. }
  147. return position;
  148. },
  149. /**
  150. * Finds the better widget's position when its anchor is outside
  151. * the view port.
  152. *
  153. * @method _findBestPositionOutsideViewport
  154. * @param node
  155. * @protected
  156. */
  157. _findBestPositionOutsideViewport: function(node) {
  158. var instance = this,
  159. nodeRegion = instance._getRegion(node),
  160. region = instance._getRegion();
  161. if (nodeRegion.top < region.top) {
  162. return 'bottom';
  163. }
  164. else if (nodeRegion.bottom > region.bottom) {
  165. return 'top';
  166. }
  167. else if (nodeRegion.right > region.right) {
  168. return 'left';
  169. }
  170. else if (nodeRegion.left < region.left) {
  171. return 'right';
  172. }
  173. },
  174. /**
  175. * Guess alignment points for the `position`.
  176. *
  177. * @method _getAlignPointsSuggestion
  178. * @attribute position
  179. * @protected
  180. */
  181. _getAlignPointsSuggestion: function(position) {
  182. return this.POSITION_ALIGN_SUGGESTION[position];
  183. },
  184. /**
  185. * Set the `position` attribute.
  186. *
  187. * @method _getPosition
  188. * @param {Number} val
  189. * @protected
  190. */
  191. _getPosition: function(val) {
  192. if (A.Lang.isFunction(val)) {
  193. val = val.call(this);
  194. }
  195. return val;
  196. },
  197. /**
  198. * Fire before `_uiSetAlign` method.
  199. *
  200. * @method _onUISetAlignPAS
  201. * @param node
  202. * @protected
  203. */
  204. _onUISetAlignPAS: function(node) {
  205. var instance = this,
  206. position;
  207. if (!instance.get('constrain')) {
  208. return;
  209. }
  210. position = instance._findBestPosition(node);
  211. instance._syncPositionUI(
  212. position, instance._lastPosition || instance.get('position'));
  213. instance._lastPosition = position;
  214. return new A.Do.AlterArgs(
  215. null, [node, instance._getAlignPointsSuggestion(position)]);
  216. },
  217. /**
  218. * Sets the position according to the align points initially defined.
  219. *
  220. * @method _setPositionAccordingPoints
  221. * @protected
  222. */
  223. _setPositionAccordingPoints: function() {
  224. var instance = this,
  225. points = instance.get('align').points;
  226. A.Object.some(instance.POSITION_ALIGN_SUGGESTION, function(value, key) {
  227. if (points[0] === value[0] && points[1] === value[1]) {
  228. instance.set('position', key);
  229. return true;
  230. }
  231. });
  232. },
  233. /**
  234. * Sync the `boundingBox` position CSS classes.
  235. *
  236. * @method _syncPositionUI
  237. * @param val
  238. * @param prevVal
  239. * @protected
  240. */
  241. _syncPositionUI: function(val, prevVal) {
  242. var instance = this,
  243. boundingBox = instance.get('boundingBox');
  244. if (prevVal) {
  245. boundingBox.removeClass(getClassName(prevVal));
  246. }
  247. boundingBox.addClass(getClassName(val));
  248. },
  249. /**
  250. * Set the `boundingBox` position on the UI.
  251. *
  252. * @method _uiSetPosition
  253. * @param val
  254. * @param prevVal
  255. * @protected
  256. */
  257. _uiSetPosition: function(val, prevVal) {
  258. var instance = this;
  259. instance._syncPositionUI(val, prevVal);
  260. instance.suggestAlignment();
  261. },
  262. /**
  263. * Validates the value of `position` attribute.
  264. *
  265. * @method _validatePosition
  266. * @param value
  267. * @protected
  268. * @return {Boolean} True only if value is 'bottom', 'top', 'left'
  269. * or 'right'.
  270. */
  271. _validatePosition: function(val) {
  272. if (A.Lang.isFunction(val)) {
  273. val = val.call(this);
  274. }
  275. return (val === 'bottom' || val === 'top' || val === 'left' || val === 'right');
  276. }
  277. });
  278. A.WidgetPositionAlignSuggestion = PositionAlignSuggestion;