keeweb/app/scripts/util/tip.js

181 lines
5.0 KiB
JavaScript
Raw Normal View History

2017-01-31 07:50:28 +01:00
const Backbone = require('backbone');
const FeatureDetector = require('./feature-detector');
2016-01-13 19:21:48 +01:00
2017-01-31 07:50:28 +01:00
const Tip = function(el, config) {
2016-01-10 21:58:21 +01:00
this.el = el;
2019-08-16 23:05:39 +02:00
this.title = (config && config.title) || el.attr('title');
this.placement = (config && config.placement) || el.attr('tip-placement');
this.fast = (config && config.fast) || false;
2016-01-10 21:58:21 +01:00
this.tipEl = null;
this.showTimeout = null;
this.hideTimeout = null;
2019-08-16 23:05:39 +02:00
this.force = (config && config.force) || false;
2016-02-04 20:48:25 +01:00
this.hide = this.hide.bind(this);
2016-01-10 21:58:21 +01:00
};
2016-07-17 21:08:23 +02:00
Tip.enabled = !FeatureDetector.isMobile;
2016-01-13 19:21:48 +01:00
2016-01-10 21:58:21 +01:00
Tip.prototype.init = function() {
2016-01-13 19:21:48 +01:00
if (!Tip.enabled) {
return;
}
2016-01-10 21:58:21 +01:00
this.el.removeAttr('title');
2016-02-28 11:36:56 +01:00
this.el.attr('data-title', this.title);
2016-01-10 21:58:21 +01:00
this.el.mouseenter(this.mouseenter.bind(this)).mouseleave(this.mouseleave.bind(this));
2016-01-13 21:33:14 +01:00
this.el.click(this.mouseleave.bind(this));
2016-01-10 21:58:21 +01:00
};
2016-01-11 18:56:52 +01:00
Tip.prototype.show = function() {
2019-08-16 23:05:39 +02:00
if ((!Tip.enabled && !this.force) || !this.title) {
2016-01-13 19:21:48 +01:00
return;
}
2016-02-04 20:48:25 +01:00
Backbone.on('page-geometry', this.hide);
2016-01-11 18:56:52 +01:00
if (this.tipEl) {
this.tipEl.remove();
if (this.hideTimeout) {
clearTimeout(this.hideTimeout);
this.hideTimeout = null;
}
}
2019-08-16 23:05:39 +02:00
const tipEl = (this.tipEl = $('<div></div>')
.addClass('tip')
.appendTo('body')
.html(this.title));
2017-01-31 07:50:28 +01:00
const rect = this.el[0].getBoundingClientRect();
const tipRect = this.tipEl[0].getBoundingClientRect();
const placement = this.placement || this.getAutoPlacement(rect, tipRect);
2016-01-11 18:56:52 +01:00
tipEl.addClass('tip--' + placement);
2016-01-12 22:08:24 +01:00
if (this.fast) {
tipEl.addClass('tip--fast');
}
2019-08-16 23:05:39 +02:00
let top, left;
2017-01-31 07:50:28 +01:00
const offset = 10;
const sideOffset = 10;
2016-01-11 18:56:52 +01:00
switch (placement) {
case 'top':
top = rect.top - tipRect.height - offset;
left = rect.left + rect.width / 2 - tipRect.width / 2;
break;
2016-02-04 20:38:57 +01:00
case 'top-left':
top = rect.top - tipRect.height - offset;
left = rect.left + rect.width / 2 - tipRect.width + sideOffset;
break;
2016-01-11 18:56:52 +01:00
case 'bottom':
top = rect.bottom + offset;
left = rect.left + rect.width / 2 - tipRect.width / 2;
break;
case 'left':
top = rect.top + rect.height / 2 - tipRect.height / 2;
left = rect.left - tipRect.width - offset;
break;
case 'right':
top = rect.top + rect.height / 2 - tipRect.height / 2;
left = rect.right + offset;
break;
}
tipEl.css({ top: top, left: left });
};
Tip.prototype.hide = function() {
if (this.tipEl) {
this.tipEl.remove();
this.tipEl = null;
}
2016-02-04 20:48:25 +01:00
Backbone.off('page-geometry', this.hide);
2016-01-11 18:56:52 +01:00
};
2016-01-10 21:58:21 +01:00
Tip.prototype.mouseenter = function() {
if (this.showTimeout) {
return;
}
2016-07-17 13:30:38 +02:00
this.showTimeout = setTimeout(() => {
this.showTimeout = null;
this.show();
2016-01-10 21:58:21 +01:00
}, 200);
};
Tip.prototype.mouseleave = function() {
if (this.tipEl) {
2016-07-17 13:30:38 +02:00
this.tipEl.addClass('tip--hide');
this.hideTimeout = setTimeout(() => {
this.hideTimeout = null;
this.hide();
2016-01-10 21:58:21 +01:00
}, 500);
}
if (this.showTimeout) {
clearTimeout(this.showTimeout);
this.showTimeout = null;
}
};
Tip.prototype.getAutoPlacement = function(rect, tipRect) {
2017-01-31 07:50:28 +01:00
const padding = 20;
const bodyRect = document.body.getBoundingClientRect();
const canShowToBottom = bodyRect.bottom - rect.bottom > padding + tipRect.height;
const canShowToHalfRight = bodyRect.right - rect.right > padding + tipRect.width / 2;
const canShowToRight = bodyRect.right - rect.right > padding + tipRect.width;
const canShowToHalfLeft = rect.left > padding + tipRect.width / 2;
const canShowToLeft = rect.left > padding + tipRect.width;
2016-01-10 21:58:21 +01:00
if (canShowToBottom) {
if (canShowToLeft && !canShowToHalfRight) {
return 'left';
} else if (canShowToRight && !canShowToHalfLeft) {
return 'right';
} else {
return 'bottom';
}
}
if (canShowToLeft && !canShowToHalfRight) {
return 'left';
} else if (canShowToRight && !canShowToHalfLeft) {
return 'right';
} else {
return 'top';
}
};
Tip.createTips = function(container) {
2016-01-13 19:21:48 +01:00
if (!Tip.enabled) {
return;
}
2016-07-17 13:30:38 +02:00
container.find('[title]').each((ix, el) => {
2016-06-04 14:47:56 +02:00
Tip.createTip(el);
2016-02-28 11:36:56 +01:00
});
};
2017-04-16 10:19:46 +02:00
Tip.createTip = function(el, options) {
2017-04-16 11:53:14 +02:00
if (!Tip.enabled && (!options || !options.force)) {
2016-06-04 14:47:56 +02:00
return;
}
2017-04-16 10:19:46 +02:00
const tip = new Tip($(el), options);
if (!options || !options.noInit) {
tip.init();
}
2016-06-04 14:47:56 +02:00
el._tip = tip;
2017-04-16 10:19:46 +02:00
return tip;
2016-06-04 14:47:56 +02:00
};
2016-02-28 11:36:56 +01:00
Tip.hideTips = function(container) {
if (!Tip.enabled) {
return;
}
2016-07-17 13:30:38 +02:00
container.find('[data-title]').each((ix, el) => {
2016-06-04 14:47:56 +02:00
Tip.hideTip(el);
2016-01-10 21:58:21 +01:00
});
};
2016-06-04 14:47:56 +02:00
Tip.hideTip = function(el) {
if (el._tip) {
el._tip.hide();
}
};
Tip.updateTip = function(el, props) {
if (el._tip) {
el._tip.hide();
2019-08-16 23:05:39 +02:00
_.extend(el._tip, _.pick(props, ['title', 'placement', 'fast', 'showTimeout', 'hideTimeout']));
}
};
2016-01-10 21:58:21 +01:00
module.exports = Tip;