var getClassName = A.getClassName;
/**
* Widget extension, which can be used to suggest alignment points based on
* position attribute to base Widget class, through the
* [Base.build](Base.html#method_build) method. It also tries to find
* the best position in case the widget doesn't fit it's constrainment node.
*
* @class A.WidgetPositionAlignSuggestion
* @param {Object} The user configuration object
*/
function PositionAlignSuggestion() {}
/**
* Static property used to define the default attribute
* configuration.
*
* @property ATTRS
* @type Object
* @static
*/
PositionAlignSuggestion.ATTRS = {
/**
* Determine the position of the tooltip.
*
* @attribute position
* @default top
* @type {String}
*/
position: {
getter: '_getPosition',
validator: '_validatePosition',
value: 'top'
}
};
A.mix(PositionAlignSuggestion.prototype, {
/**
* Property defining the align points based on the suggested `position`.
*
* @property POSITION_ALIGN_SUGGESTION
* @type {}
*/
POSITION_ALIGN_SUGGESTION: {
bottom: [A.WidgetPositionAlign.TC, A.WidgetPositionAlign.BC],
left: [A.WidgetPositionAlign.RC, A.WidgetPositionAlign.LC],
right: [A.WidgetPositionAlign.LC, A.WidgetPositionAlign.RC],
top: [A.WidgetPositionAlign.BC, A.WidgetPositionAlign.TC]
},
_hasAlignmentPoints: false,
_lastPosition: null,
/**
* Construction logic executed during WidgetPositionAlignSuggestion
* instantiation. Lifecycle.
*
* @method initializer
* @protected
*/
initializer: function(config) {
var instance = this;
if (config && config.align && config.align.points) {
instance._hasAlignmentPoints = true;
instance._setPositionAccordingPoints();
}
A.on(instance._onUISetAlignPAS, instance, '_uiSetAlign');
A.after(instance._afterRenderUIPAS, instance, 'renderUI');
instance.after('positionChange', instance._afterPositionChangePAS);
},
/**
* Suggest alignment for the node based on the `position` suggestion.
*
* @method suggestAlignment
* @attribute alignNode
*/
suggestAlignment: function(alignNode) {
var instance = this,
align;
align = instance.get('align') || {};
if (alignNode) {
align.node = alignNode;
}
if (!instance._hasAlignmentPoints) {
align.points = instance._getAlignPointsSuggestion(
instance.get('position'));
}
instance.set('align', align);
},
/**
* Fire after `boundingBox` position changes.
*
* @method _afterPositionChangePAS
* @param event
* @protected
*/
_afterPositionChangePAS: function(event) {
var instance = this;
instance._uiSetPosition(event.newVal, event.prevVal);
},
/**
* Fire after `renderUI` method.
*
* @method _afterRenderUIPAS
* @param event
* @protected
*/
_afterRenderUIPAS: function() {
var instance = this;
instance._uiSetPosition(instance.get('position'));
},
/**
* Returns true if the widget can fit inside it's constrainment node.
*
* @method _canWidgetAlignToNode
* @param node
* @param position
* @protected
*/
_canWidgetAlignToNode: function(node, position) {
var instance = this,
constrainedXY,
points = instance._getAlignPointsSuggestion(position),
xy = instance._getAlignedXY(node, points);
constrainedXY = instance.getConstrainedXY(xy);
return (constrainedXY[0] === xy[0] && constrainedXY[1] === xy[1]);
},
/**
* Finds the position in which the widget fits without having to have its
* coordinates changed due to its constrainment node.
*
* @method _findBestPosition
* @param node
* @protected
*/
_findBestPosition: function(node) {
var instance = this,
position = instance.get('position'),
testPositions = [position, 'top', 'bottom', 'right', 'left'],
trigger = A.one(node);
if (trigger && !trigger.inViewportRegion()) {
return instance._findBestPositionOutsideViewport(trigger);
} else {
testPositions = A.Array.dedupe(testPositions);
A.Array.some(testPositions, function(testPosition) {
if (instance._canWidgetAlignToNode(trigger, testPosition)) {
position = testPosition;
return true;
}
});
}
return position;
},
/**
* Finds the better widget's position when its anchor is outside
* the view port.
*
* @method _findBestPositionOutsideViewport
* @param node
* @protected
*/
_findBestPositionOutsideViewport: function(node) {
var instance = this,
nodeRegion = instance._getRegion(node),
region = instance._getRegion();
if (nodeRegion.top < region.top) {
return 'bottom';
}
else if (nodeRegion.bottom > region.bottom) {
return 'top';
}
else if (nodeRegion.right > region.right) {
return 'left';
}
else if (nodeRegion.left < region.left) {
return 'right';
}
},
/**
* Guess alignment points for the `position`.
*
* @method _getAlignPointsSuggestion
* @attribute position
* @protected
*/
_getAlignPointsSuggestion: function(position) {
return this.POSITION_ALIGN_SUGGESTION[position];
},
/**
* Set the `position` attribute.
*
* @method _getPosition
* @param {Number} val
* @protected
*/
_getPosition: function(val) {
if (A.Lang.isFunction(val)) {
val = val.call(this);
}
return val;
},
/**
* Fire before `_uiSetAlign` method.
*
* @method _onUISetAlignPAS
* @param node
* @protected
*/
_onUISetAlignPAS: function(node) {
var instance = this,
position;
if (!instance.get('constrain')) {
return;
}
position = instance._findBestPosition(node);
instance._syncPositionUI(
position, instance._lastPosition || instance.get('position'));
instance._lastPosition = position;
return new A.Do.AlterArgs(
null, [node, instance._getAlignPointsSuggestion(position)]);
},
/**
* Sets the position according to the align points initially defined.
*
* @method _setPositionAccordingPoints
* @protected
*/
_setPositionAccordingPoints: function() {
var instance = this,
points = instance.get('align').points;
A.Object.some(instance.POSITION_ALIGN_SUGGESTION, function(value, key) {
if (points[0] === value[0] && points[1] === value[1]) {
instance.set('position', key);
return true;
}
});
},
/**
* Sync the `boundingBox` position CSS classes.
*
* @method _syncPositionUI
* @param val
* @param prevVal
* @protected
*/
_syncPositionUI: function(val, prevVal) {
var instance = this,
boundingBox = instance.get('boundingBox');
if (prevVal) {
boundingBox.removeClass(getClassName(prevVal));
}
boundingBox.addClass(getClassName(val));
},
/**
* Set the `boundingBox` position on the UI.
*
* @method _uiSetPosition
* @param val
* @param prevVal
* @protected
*/
_uiSetPosition: function(val, prevVal) {
var instance = this;
instance._syncPositionUI(val, prevVal);
instance.suggestAlignment();
},
/**
* Validates the value of `position` attribute.
*
* @method _validatePosition
* @param value
* @protected
* @return {Boolean} True only if value is 'bottom', 'top', 'left'
* or 'right'.
*/
_validatePosition: function(val) {
if (A.Lang.isFunction(val)) {
val = val.call(this);
}
return (val === 'bottom' || val === 'top' || val === 'left' || val === 'right');
}
});
A.WidgetPositionAlignSuggestion = PositionAlignSuggestion;