165 lines
4.5 KiB
JavaScript
165 lines
4.5 KiB
JavaScript
|
var _ = require('underscore');
|
||
|
var CoreView = require('backbone/core-view');
|
||
|
require('tipsy');
|
||
|
|
||
|
/**
|
||
|
* Tipsy tooltip view.
|
||
|
*
|
||
|
* - Needs an element to work.
|
||
|
* - Inits tipsy library.
|
||
|
* - Clean bastard tipsy bindings easily.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
module.exports = CoreView.extend({
|
||
|
options: {
|
||
|
gravity: 's',
|
||
|
opacity: 1,
|
||
|
fade: true
|
||
|
},
|
||
|
|
||
|
events: {
|
||
|
'mouseenter': '_onMouseEnter',
|
||
|
'mouseleave': '_onMouseLeave'
|
||
|
},
|
||
|
|
||
|
initialize: function (opts) {
|
||
|
if (!opts.el) throw new Error('Element is needed to have tipsy tooltip working');
|
||
|
|
||
|
this._mouseEnterAction = opts.mouseEnterAction;
|
||
|
this._mouseLeaveAction = opts.mouseLeaveAction;
|
||
|
this._tipsyOpenedManually = opts.trigger === 'manual';
|
||
|
|
||
|
this._initTipsy();
|
||
|
},
|
||
|
|
||
|
_initTipsy: function () {
|
||
|
var options = _.clone(this.options);
|
||
|
|
||
|
if (options.gravity === 'auto') {
|
||
|
options.gravity = this.getOptimalGravity(
|
||
|
options.gravityConfiguration && options.gravityConfiguration.preferredGravities || ['s', 'n', 'e', 'w'],
|
||
|
options.gravityConfiguration && options.gravityConfiguration.margin || 0,
|
||
|
window
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (!options.title) {
|
||
|
options.title = this.getTitleFromDataAttribute;
|
||
|
}
|
||
|
|
||
|
this.$el.tipsy(options);
|
||
|
this.tipsy = this.$el.data('tipsy');
|
||
|
},
|
||
|
|
||
|
_onMouseEnter: function () {
|
||
|
this._mouseEnterAction && this._mouseEnterAction();
|
||
|
},
|
||
|
|
||
|
_onMouseLeave: function () {
|
||
|
this._mouseLeaveAction && this._mouseLeaveAction();
|
||
|
},
|
||
|
|
||
|
setOffset: function (offset) {
|
||
|
this.tipsy.options.offset = offset;
|
||
|
},
|
||
|
|
||
|
showTipsy: function () {
|
||
|
this.$el.tipsy('show');
|
||
|
},
|
||
|
|
||
|
hideTipsy: function () {
|
||
|
this.$el.tipsy('hide');
|
||
|
},
|
||
|
|
||
|
getElement: function () {
|
||
|
return this.el;
|
||
|
},
|
||
|
|
||
|
getOptimalGravity: function (preferredGravities, margin, browserWindow) {
|
||
|
return function (tooltipElement) {
|
||
|
if (!tooltipElement) {
|
||
|
return preferredGravities[0] || 'n';
|
||
|
}
|
||
|
|
||
|
var tooltipContainer = this;
|
||
|
var tooltipContainerBoundingRect = tooltipContainer.getBoundingClientRect();
|
||
|
var gravityOptions = { n: 'bottom', s: 'top', w: 'right', e: 'left' };
|
||
|
|
||
|
var viewportBoundaries = {
|
||
|
top: browserWindow.pageYOffset + margin,
|
||
|
right: browserWindow.innerWidth + browserWindow.pageXOffset - margin,
|
||
|
bottom: browserWindow.innerHeight + browserWindow.pageYOffset - margin,
|
||
|
left: browserWindow.pageXOffset + margin
|
||
|
};
|
||
|
|
||
|
var tooltipBoundaries = {
|
||
|
top: tooltipContainerBoundingRect.top - tooltipElement.offsetHeight,
|
||
|
right: tooltipContainerBoundingRect.left + tooltipContainerBoundingRect.width + tooltipElement.offsetWidth,
|
||
|
bottom: tooltipContainerBoundingRect.top + tooltipContainerBoundingRect.height + tooltipElement.offsetHeight,
|
||
|
left: tooltipContainerBoundingRect.left - tooltipElement.offsetWidth
|
||
|
};
|
||
|
|
||
|
var tooltipBodyGravity = '';
|
||
|
var optimalGravity = _.find(preferredGravities, function (gravity) {
|
||
|
var position = gravityOptions[gravity];
|
||
|
var tooltipFitsPosition = isTooltipInsideViewport(position, viewportBoundaries[position], tooltipBoundaries[position]);
|
||
|
|
||
|
if (tooltipFitsPosition && (position === 'top' || position === 'bottom')) {
|
||
|
tooltipBodyGravity = getTooltipBodyGravity(gravityOptions, viewportBoundaries, tooltipBoundaries);
|
||
|
}
|
||
|
|
||
|
return tooltipFitsPosition;
|
||
|
});
|
||
|
|
||
|
return optimalGravity ? optimalGravity + tooltipBodyGravity : preferredGravities[0];
|
||
|
};
|
||
|
},
|
||
|
|
||
|
getTitleFromDataAttribute: function () {
|
||
|
return this.getAttribute('data-tooltip');
|
||
|
},
|
||
|
|
||
|
destroyTipsy: function () {
|
||
|
if (this.tipsy) {
|
||
|
// tipsy does not return this
|
||
|
this.tipsy.hide();
|
||
|
this.$el.unbind('mouseleave mouseenter');
|
||
|
}
|
||
|
|
||
|
if (this._tipsyOpenedManually) {
|
||
|
this.hideTipsy();
|
||
|
}
|
||
|
|
||
|
this.$el.removeData('tipsy');
|
||
|
delete this.tipsy;
|
||
|
},
|
||
|
|
||
|
clean: function () {
|
||
|
this.destroyTipsy();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
var isTooltipInsideViewport = function (bound, viewportBound, tooltipBound) {
|
||
|
if (bound === 'top' || bound === 'left') {
|
||
|
return viewportBound <= tooltipBound;
|
||
|
}
|
||
|
|
||
|
if (bound === 'right' || bound === 'bottom') {
|
||
|
return viewportBound >= tooltipBound;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var getTooltipBodyGravity = function (gravityOptions, viewportBoundaries, tooltipBoundaries) {
|
||
|
var nonCollisioningEdges = _.filter(['e', 'w'], function (edge) {
|
||
|
var bound = gravityOptions[edge];
|
||
|
return isTooltipInsideViewport(bound, viewportBoundaries[bound], tooltipBoundaries[bound]);
|
||
|
});
|
||
|
|
||
|
if (nonCollisioningEdges.length === 1) {
|
||
|
return nonCollisioningEdges[0];
|
||
|
}
|
||
|
|
||
|
return '';
|
||
|
};
|