API Docs for: 3.8.0
Show:

File: widget-position-constrain/js/Widget-PositionConstrain.js

  1. /**
  2.  * Provides constrained xy positioning support for Widgets, through an extension.
  3.  *
  4.  * It builds on top of the widget-position module, to provide constrained positioning support.
  5.  *
  6.  * @module widget-position-constrain
  7.  */
  8. var CONSTRAIN = "constrain",
  9.     CONSTRAIN_XYCHANGE = "constrain|xyChange",
  10.     CONSTRAIN_CHANGE = "constrainChange",

  11.     PREVENT_OVERLAP = "preventOverlap",
  12.     ALIGN = "align",
  13.    
  14.     EMPTY_STR = "",

  15.     BINDUI = "bindUI",

  16.     XY = "xy",
  17.     X_COORD = "x",
  18.     Y_COORD = "y",

  19.     Node = Y.Node,

  20.     VIEWPORT_REGION = "viewportRegion",
  21.     REGION = "region",

  22.     PREVENT_OVERLAP_MAP;

  23. /**
  24.  * A widget extension, which can be used to add constrained xy positioning support to the base Widget class,
  25.  * through the <a href="Base.html#method_build">Base.build</a> method. This extension requires that
  26.  * the WidgetPosition extension be added to the Widget (before WidgetPositionConstrain, if part of the same
  27.  * extension list passed to Base.build).
  28.  *
  29.  * @class WidgetPositionConstrain
  30.  * @param {Object} User configuration object
  31.  */
  32. function PositionConstrain(config) {
  33.     if (!this._posNode) {
  34.         Y.error("WidgetPosition needs to be added to the Widget, before WidgetPositionConstrain is added");
  35.     }
  36.     Y.after(this._bindUIPosConstrained, this, BINDUI);
  37. }

  38. /**
  39.  * Static property used to define the default attribute
  40.  * configuration introduced by WidgetPositionConstrain.
  41.  *
  42.  * @property ATTRS
  43.  * @type Object
  44.  * @static
  45.  */
  46. PositionConstrain.ATTRS = {

  47.     /**
  48.      * @attribute constrain
  49.      * @type boolean | Node
  50.      * @default null
  51.      * @description The node to constrain the widget's bounding box to, when setting xy. Can also be
  52.      * set to true, to constrain to the viewport.
  53.      */
  54.     constrain : {
  55.         value: null,
  56.         setter: "_setConstrain"
  57.     },

  58.     /**
  59.      * @attribute preventOverlap
  60.      * @type boolean
  61.      * @description If set to true, and WidgetPositionAlign is also added to the Widget,
  62.      * constrained positioning will attempt to prevent the widget's bounding box from overlapping
  63.      * the element to which it has been aligned, by flipping the orientation of the alignment
  64.      * for corner based alignments  
  65.      */
  66.     preventOverlap : {
  67.         value:false
  68.     }
  69. };

  70. /**
  71.  * @property _PREVENT_OVERLAP
  72.  * @static
  73.  * @protected
  74.  * @type Object
  75.  * @description The set of positions for which to prevent
  76.  * overlap.
  77.  */
  78. PREVENT_OVERLAP_MAP = PositionConstrain._PREVENT_OVERLAP = {
  79.     x: {
  80.         "tltr": 1,
  81.         "blbr": 1,
  82.         "brbl": 1,
  83.         "trtl": 1
  84.     },
  85.     y : {
  86.         "trbr": 1,
  87.         "tlbl": 1,
  88.         "bltl": 1,
  89.         "brtr": 1
  90.     }
  91. };

  92. PositionConstrain.prototype = {

  93.     /**
  94.      * Calculates the constrained positions for the XY positions provided, using
  95.      * the provided node argument is passed in. If no node value is passed in, the value of
  96.      * the "constrain" attribute is used.
  97.      *
  98.      * @method getConstrainedXY
  99.      * @param {Array} xy The xy values to constrain
  100.      * @param {Node | boolean} node Optional. The node to constrain to, or true for the viewport
  101.      * @return {Array} The constrained xy values
  102.      */
  103.     getConstrainedXY : function(xy, node) {
  104.         node = node || this.get(CONSTRAIN);

  105.         var constrainingRegion = this._getRegion((node === true) ? null : node),
  106.             nodeRegion = this._posNode.get(REGION);

  107.         return [
  108.             this._constrain(xy[0], X_COORD, nodeRegion, constrainingRegion),
  109.             this._constrain(xy[1], Y_COORD, nodeRegion, constrainingRegion)
  110.         ];
  111.     },

  112.     /**
  113.      * Constrains the widget's bounding box to a node (or the viewport). If xy or node are not
  114.      * passed in, the current position and the value of "constrain" will be used respectively.
  115.      *
  116.      * The widget's position will be changed to the constrained position.
  117.      *
  118.      * @method constrain
  119.      * @param {Array} xy Optional. The xy values to constrain
  120.      * @param {Node | boolean} node Optional. The node to constrain to, or true for the viewport
  121.      */
  122.     constrain : function(xy, node) {
  123.         var currentXY,
  124.             constrainedXY,
  125.             constraint = node || this.get(CONSTRAIN);

  126.         if (constraint) {
  127.             currentXY = xy || this.get(XY);
  128.             constrainedXY = this.getConstrainedXY(currentXY, constraint);

  129.             if (constrainedXY[0] !== currentXY[0] || constrainedXY[1] !== currentXY[1]) {
  130.                 this.set(XY, constrainedXY, { constrained:true });
  131.             }
  132.         }
  133.     },

  134.     /**
  135.      * The setter implementation for the "constrain" attribute.
  136.      *
  137.      * @method _setConstrain
  138.      * @protected
  139.      * @param {Node | boolean} val The attribute value
  140.      */
  141.     _setConstrain : function(val) {
  142.         return (val === true) ? val : Node.one(val);
  143.     },

  144.     /**
  145.      * The method which performs the actual constrain calculations for a given axis ("x" or "y") based
  146.      * on the regions provided.
  147.      *
  148.      * @method _constrain
  149.      * @protected
  150.      *
  151.      * @param {Number} val The value to constrain
  152.      * @param {String} axis The axis to use for constrainment
  153.      * @param {Region} nodeRegion The region of the node to constrain
  154.      * @param {Region} constrainingRegion The region of the node (or viewport) to constrain to
  155.      *
  156.      * @return {Number} The constrained value
  157.      */
  158.     _constrain: function(val, axis, nodeRegion, constrainingRegion) {
  159.         if (constrainingRegion) {

  160.             if (this.get(PREVENT_OVERLAP)) {
  161.                 val = this._preventOverlap(val, axis, nodeRegion, constrainingRegion);
  162.             }

  163.             var x = (axis == X_COORD),

  164.                 regionSize    = (x) ? constrainingRegion.width : constrainingRegion.height,
  165.                 nodeSize      = (x) ? nodeRegion.width : nodeRegion.height,
  166.                 minConstraint = (x) ? constrainingRegion.left : constrainingRegion.top,
  167.                 maxConstraint = (x) ? constrainingRegion.right - nodeSize : constrainingRegion.bottom - nodeSize;

  168.             if (val < minConstraint || val > maxConstraint) {
  169.                 if (nodeSize < regionSize) {
  170.                     if (val < minConstraint) {
  171.                         val = minConstraint;
  172.                     } else if (val > maxConstraint) {
  173.                         val = maxConstraint;
  174.                     }
  175.                 } else {
  176.                     val = minConstraint;
  177.                 }
  178.             }
  179.         }

  180.         return val;
  181.     },

  182.     /**
  183.      * The method which performs the preventOverlap calculations for a given axis ("x" or "y") based
  184.      * on the value and regions provided.
  185.      *
  186.      * @method _preventOverlap
  187.      * @protected
  188.      *
  189.      * @param {Number} val The value being constrain
  190.      * @param {String} axis The axis to being constrained
  191.      * @param {Region} nodeRegion The region of the node being constrained
  192.      * @param {Region} constrainingRegion The region of the node (or viewport) we need to constrain to
  193.      *
  194.      * @return {Number} The constrained value
  195.      */
  196.     _preventOverlap : function(val, axis, nodeRegion, constrainingRegion) {

  197.         var align = this.get(ALIGN),
  198.             x = (axis === X_COORD),
  199.             nodeSize,
  200.             alignRegion,
  201.             nearEdge,
  202.             farEdge,
  203.             spaceOnNearSide,
  204.             spaceOnFarSide;

  205.         if (align && align.points && PREVENT_OVERLAP_MAP[axis][align.points.join(EMPTY_STR)]) {

  206.             alignRegion = this._getRegion(align.node);

  207.             if (alignRegion) {
  208.                 nodeSize        = (x) ? nodeRegion.width : nodeRegion.height;
  209.                 nearEdge        = (x) ? alignRegion.left : alignRegion.top;
  210.                 farEdge         = (x) ? alignRegion.right : alignRegion.bottom;
  211.                 spaceOnNearSide = (x) ? alignRegion.left - constrainingRegion.left : alignRegion.top - constrainingRegion.top;
  212.                 spaceOnFarSide  = (x) ? constrainingRegion.right - alignRegion.right : constrainingRegion.bottom - alignRegion.bottom;
  213.             }
  214.  
  215.             if (val > nearEdge) {
  216.                 if (spaceOnFarSide < nodeSize && spaceOnNearSide > nodeSize) {
  217.                     val = nearEdge - nodeSize;
  218.                 }
  219.             } else {
  220.                 if (spaceOnNearSide < nodeSize && spaceOnFarSide > nodeSize) {
  221.                     val = farEdge;
  222.                 }
  223.             }
  224.         }

  225.         return val;
  226.     },

  227.     /**
  228.      * Binds event listeners responsible for updating the UI state in response to
  229.      * Widget constrained positioning related state changes.
  230.      * <p>
  231.      * This method is invoked after bindUI is invoked for the Widget class
  232.      * using YUI's aop infrastructure.
  233.      * </p>
  234.      *
  235.      * @method _bindUIPosConstrained
  236.      * @protected
  237.      */
  238.     _bindUIPosConstrained : function() {
  239.         this.after(CONSTRAIN_CHANGE, this._afterConstrainChange);
  240.         this._enableConstraints(this.get(CONSTRAIN));
  241.     },

  242.     /**
  243.      * After change listener for the "constrain" attribute, responsible
  244.      * for updating the UI, in response to attribute changes.
  245.      *
  246.      * @method _afterConstrainChange
  247.      * @protected
  248.      * @param {EventFacade} e The event facade
  249.      */
  250.     _afterConstrainChange : function(e) {
  251.         this._enableConstraints(e.newVal);
  252.     },

  253.     /**
  254.      * Updates the UI if enabling constraints, and sets up the xyChange event listeners
  255.      * to constrain whenever the widget is moved. Disabling constraints removes the listeners.
  256.      *
  257.      * @method enable or disable constraints listeners
  258.      * @private
  259.      * @param {boolean} enable Enable or disable constraints
  260.      */
  261.     _enableConstraints : function(enable) {
  262.         if (enable) {
  263.             this.constrain();
  264.             this._cxyHandle = this._cxyHandle || this.on(CONSTRAIN_XYCHANGE, this._constrainOnXYChange);
  265.         } else if (this._cxyHandle) {
  266.             this._cxyHandle.detach();
  267.             this._cxyHandle = null;    
  268.         }
  269.     },

  270.     /**
  271.      * The on change listener for the "xy" attribute. Modifies the event facade's
  272.      * newVal property with the constrained XY value.
  273.      *
  274.      * @method _constrainOnXYChange
  275.      * @protected
  276.      * @param {EventFacade} e The event facade for the attribute change
  277.      */
  278.     _constrainOnXYChange : function(e) {
  279.         if (!e.constrained) {
  280.             e.newVal = this.getConstrainedXY(e.newVal);
  281.         }
  282.     },

  283.     /**
  284.      * Utility method to normalize region retrieval from a node instance,
  285.      * or the viewport, if no node is provided.
  286.      *
  287.      * @method _getRegion
  288.      * @private
  289.      * @param {Node} node Optional.
  290.      */
  291.     _getRegion : function(node) {
  292.         var region;
  293.         if (!node) {
  294.             region = this._posNode.get(VIEWPORT_REGION);
  295.         } else {
  296.             node = Node.one(node);
  297.             if (node) {
  298.                 region = node.get(REGION);
  299.             }
  300.         }
  301.         return region;
  302.     }
  303. };

  304. Y.WidgetPositionConstrain = PositionConstrain;

  305.