kdf
Version:
300 lines (219 loc) • 7.98 kB
text/coffeescript
###
KDTooltip
A tooltip has a position and a direction, relative to the delegate
element it is attached to.
Valid positioning types are 'top','bottom','left' and 'right'
Valid direction types are 'top','bottom','left','right' and 'center'
Should a tooltip move off-screen, it will be relocated to be fully
visible.
###
KDView = require './../../core/view.coffee'
module.exports = class KDTooltip extends KDView
constructor:(options,data)->
options.bind or= 'mouseenter mouseleave'
options.sticky ?= no
options.cssClass = KD.utils.curry 'kdtooltip', options.cssClass
super options, data
= no
=
= new KDView cssClass : 'wrapper'
if .animate then 'out' else
KD.singletons.windowController.on 'ScrollHappened', "hide"
'viewAppended', =>
{view, title, html} =
if view?
view
else
'just-text'
title, html
.emit 'TooltipReady'
= yes
show:(event)->
{selector} =
return if selector
super
= yes
hide: (event)->
return unless
{permanent} =
super
.remove()
KD.singletons.windowController.removeLayer this unless permanent
= no
update: (o = , view) ->
unless view
o.selector or= null
o.title or= ""
o.title, o.html
else
view
= yes
addListeners:->
{events, sticky, permanent} =
_show = 'show'
_hide = 'hide'
.bindEvent name for name in events
.on 'mouseenter', _show
.on 'mouseleave', _hide unless sticky
'ReceivedClickElsewhere', _hide unless permanent
'KDObjectWillBeDestroyed', =>
.off 'mouseenter', _show
.off 'mouseleave', _hide unless sticky
setView: (childView) ->
return unless childView
.view.destroy() if .view?
if childView.constructorName
{options, data, constructorName} = childView
= new constructorName options, data
else
.addSubView childView
getView:->
destroy:->
.tooltip = null
delete .tooltip
super
translateCompassDirections:(o)->
{placement,gravity} = o
o.placement = placementMap[placement]
o.direction = directionMap(o.placement, gravity)
return o
display: ->
o =
{permanent, gravity, animate} =
# converts NESW-Values to topbottomleftright and retains them in
KD.singletons.windowController.addLayer this unless permanent
o = o if gravity
o.gravity = null
'in' if animate
KD.utils.defer => o
= yes
getCorrectPositionCoordinates:(o={},positionValues,callback=noop)->
# values that can/will be used in all the submethods
container = @$()
selector = .$(o.selector)
d = # dimensions
container :
height : container.height()
width : container.width()
selector :
offset : selector.offset()
height : selector.height()
width : selector.width()
{placement,direction} = positionValues
# check the default values for overlapping boundaries, then
# recalculate if there are overlaps
{forcePosition} =
violations = getBoundaryViolations getCoordsFromPlacement(d, placement, direction),\
d.container.width, d.container.height
if !forcePosition and Object.keys(violations).length > 0 # check for possible alternatives
variants = [
['top','right']
['right','top']
['right','bottom']
['bottom','right']
['top','left']
['top','center']
['right','center']
['bottom','center']
['bottom','left']
['left','bottom']
['left','center']
['left','top']
]
for variant in variants
if Object.keys(getBoundaryViolations(getCoordsFromPlacement(d,variant[0],variant[1]),\
d.container.width, d.container.height)).length is 0
[placement,direction] = variant
break
correctValues =
coords : getCoordsFromPlacement d, placement, direction
placement : placement
direction : direction
callback correctValues
return correctValues
setPositions:(o = ,animate = no)->
'animate-movement' if animate
placement = o.placement or 'top'
direction = o.direction or 'right'
offset =
if Number is typeof o.offset
top : o.offset
left : 0
else
o.offset
# Correct impossible combinations
direction =
if placement in ['top','bottom'] and direction in ['top','bottom']
'center'
else if placement in ['left','right'] and direction in ['left','right']
'center'
else direction
# fetch corrected placement and coordinated for positioning
{coords,placement,direction} = o,{placement,direction}
# css classes for arrow positioning
for placement_ in ['top','bottom','left','right']
if placement is placement_
'placement-'+placement_
else
'placement-'+placement_
for direction_ in ['top','bottom','left','right','center']
if direction is direction_
'direction-'+direction_
else
'direction-'+direction_
@$().css
left : coords.left + offset.left
top : coords.top + offset.top
.wait 500, => 'animate-movement'
setTitle: (title, isHTML) ->
title = Encoder.htmlEncode title unless isHTML
.updatePartial title
directionMap = (placement, gravity)->
if placement in ["top", "bottom"]
if /e/.test gravity then "left"
else if /w/.test gravity then "right"
else "center"
else if placement in ["left", "right"]
if /n/.test gravity then "top"
else if /s/.test gravity then "bottom"
else placement
placementMap =
top : "top"
above : "top"
below : "bottom"
bottom : "bottom"
left : "left"
right : "right"
# will return an object with the amount of clipped pixels
getBoundaryViolations = (coordinates,width,height)=>
violations = {}
if coordinates.left < 0
violations.left = -(coordinates.left)
if coordinates.top < 0
violations.top = -(coordinates.top)
if coordinates.left+width > window.innerWidth
violations.right = coordinates.left+width-window.innerWidth
if coordinates.top+height > window.innerHeight
violations.bottom = coordinates.top+height-window.innerHeight
violations
getCoordsDiff = (dimensions,type,center=no)->
diff = dimensions.selector[type]-dimensions.container[type]
if center then diff/2 else diff
getCoordsFromPlacement = (dimensions,placement,direction)->
coordinates =
top : dimensions.selector.offset.top
left : dimensions.selector.offset.left
[staticAxis,dynamicAxis,staticC,dynamicC,exclusion] =
if /o/.test placement then ['height','width','top','left','right']
else ['width','height','left','top','bottom']
coordinates[staticC]+= unless placement.length<5
(dimensions.selector[staticAxis]+10)
else -(dimensions.container[staticAxis]+10)
unless direction is exclusion
coordinates[dynamicC]+=getCoordsDiff(dimensions,dynamicAxis,direction is 'center')
return coordinates