PDA

View Full Version : [INFOREQ] Cannot align tooltip to text item (or anything actually)



19 Apr 2017, 12:28 PM
Ext JS version: 6.2.1.167

Basically there doesn't seem to be any way to get tooltips to anchor correctly via the centre of the target to the centre of the top, right or left of the tooltip. In the below example it doesn't even align correctly.

1u88

26 Apr 2017, 10:35 AM
I guess people don't make heavy use of tooltips because there's not many complaints about the fact it's broken... Here is my solution:




Ext.define('CenterAnchorTooltip', {
extend: 'Ext.tip.ToolTip',
alias: 'widget.CenterAnchorTooltip',
requires: [
'Ext.tip.ToolTip'
],

/*
* Specify the following three options when using Ext.create('CenterAnchorTooltip', { ... });
*/
side: 'x', // Which side the anchor arrow should appear on. Choices are: left, right, top, bottom. Also: x to detect left or right, y to detect top or bottom.
anchorSize: 10, // Unless you use css to change the anchor size, leave this be
alignToTargetCentre: false, // When true, don't align to the applicable side of the target, align to its centre
autoShow: true,
initComponent: function() {
let targetEl = Ext.fly(this.target);
let side = this.detectSide(targetEl);
let cfg = this.getAlignCfg(side);
let targXy = targetEl.getXY();

this.defaultAlign = cfg.align;
this.targetOffset = cfg.offset;
this.side = cfg.side || this.side;

if (!this.autoShow) {
throw new Error('CenterAnchorTooltip must always have autoShow set to true');
}

this.callParent(arguments);

/*
* If you use the events, the tooltip mysteriously just disappears - so we do it this hacky way instead
*/
Ext.defer(() => {
if (this.target) {
let anchorDom = this.getEl().down('.x-tip-anchor').dom;
anchorDom.style.position = 'fixed';

let xy = this.el.getXY();
let rightX = xy[0] + this.el.getWidth();
let bottomY = xy[1] + this.el.getHeight();
let anchorSize = this.anchorSize;
let halfAnchorSize = Math.ceil(anchorSize / 2);

let isWithinTipTop = targXy[1] >= xy[1] + halfAnchorSize;
let isWithinTipHeight = targXy[1] <= bottomY - halfAnchorSize;
let isWithinTipLeft = targXy[0] >= xy[0] + halfAnchorSize;
let isWithinTipWidth = targXy[0] <= rightX - halfAnchorSize;

let props = null;

if (this.side === 'left') {
let hasEnoughRoomOnLeft = targXy[0] <= xy[0] - anchorSize;
if (hasEnoughRoomOnLeft && isWithinTipTop && isWithinTipHeight) {
props = { border: 'Right', offset: 'top' };
anchorDom.style.marginLeft = '-' + (anchorSize * 2 + 1) + 'px';
}
} else if (this.side === 'right') {
let hasEnoughRoomOnRight = targXy[0] <= rightX + anchorSize;
if (hasEnoughRoomOnRight && isWithinTipTop && isWithinTipHeight) {
props = { border: 'Left', offset: 'top' };
anchorDom.style.marginLeft = (this.el.getWidth() - 2) + 'px';
}
} else if (this.side === 'top') {
let hasEnoughRoomOnTop = targXy[1] <= xy[1] - anchorSize;
if (hasEnoughRoomOnTop && isWithinTipLeft && isWithinTipWidth) {
props = { border: 'Bottom', offset: 'left' };
anchorDom.style.marginTop = '-' + (this.el.getHeight() + anchorSize * 2 - 1) + 'px';
}
} else if (this.side === 'bottom') {
let hasEnoughRoomOnBottom = targXy[1] <= bottomY + anchorSize;
if (hasEnoughRoomOnBottom && isWithinTipLeft && isWithinTipWidth) {
props = { border: 'Top', offset: 'left' };
anchorDom.style.marginTop = '1px';
}
}

if (props) {
anchorDom.style.visibility = 'visible';
anchorDom.style['border' + props.border] = anchorSize + 'px solid';
if (props.offset === 'top') {
anchorDom.style.top = (targXy[1] - halfAnchorSize) + 'px';
} else {
anchorDom.style.left = (targXy[0] - halfAnchorSize) + 'px';
}
}
}
}, 100);
},

detectSide: function(targetEl) {
let body = Ext.getBody();
let halfWidth = body.getWidth() / 2;
let halfHeight = body.getHeight() / 2;
let targXy = targetEl.getXY();

if (this.side === 'x') {
return targXy[0] < halfWidth ? 'left' : 'right';
} else if (this.side === 'y') {
return targXy[1] < halfHeight ? 'top' : 'bottom';
} else {
return this.side;
}
},

getAlignCfg: function(side) {
let isC = this.alignToTargetCentre;
let a = this.anchorSize;

switch (side) {
case 'left' : return { align: isC ? 'l-c' : 'l-r', offset: [ a, 0 ], side: 'left' };
case 'right' : return { align: isC ? 'r-c' : 'r-l', offset: [-a, 0 ], side: 'right' };
case 'top' : return { align: isC ? 'tc-c' : 'tc-bc', offset: [ 0, a ], side: 'top' };
case 'bottom': return { align: isC ? 'bc-c' : 'bc-tc', offset: [ 0, -a ], side: 'bottom' };
default : return { align: isC ? 'l-c' : 'l-r', offset: [ a, 0 ], side: 'left' };
}
}
});


When creating, you just pass it a "target" which is an element or dom (a per the tooltip docs). Customisations can be made to change the alignment and anchor position. If it can't decently show the anchor arrow, it won't and the tooltip will appear without an arrow.

EDIT: Oops, you'll probably wanna change "alignToTargetCentre" to "alignToTargetCenter".

8 Aug 2017, 12:58 PM
Can confirm this bug is still present in 6.5.1 (Not a problem for me due to the workaround)

evant
8 Aug 2017, 4:11 PM
I'm not really clear on what you're saying the issue is, but there's a problem with timing in your test case. The layout hasn't had a chance to complete. If you're expecting the anchor arrow to be pointing at the ">>>", then it's pretty easily done:

https://fiddle.sencha.com/#fiddle/24qd&view/editor