planit
Version:
Interactive markers on an image or blank canvas
354 lines (323 loc) • 11.3 kB
text/coffeescript
class Planit.Marker
# ======================================================== Setup
# When the Marker class is instantiated, we return the
# object, but all we need to do is set references and find
# the appropriate jQuery object.
#
# It's for this reason that the create action is a class
# method (the marker doesn't physically exist yet)
#
constructor: (@plan, id) ->
# Set Options
@container = @plan.container
@markersContainer = @container.find(".#{Planit.markerContainerClass}")
if @container.find(".#{Planit.imageContainer} > img").length > 0
@image = @container.find(".#{Planit.imageContainer} > img").first()
# Find Marker
@marker = @markersContainer.find(
".#{Planit.markerClass}[data-marker='#{id}']"
).first()
# Return this
@
# ======================================================== Create New Marker
# (class method) Creates a new marker
#
@create: (options) ->
# local references
plan = options.plan
container = plan.container
markersContainer = container.find(".#{Planit.markerContainerClass}").first()
# set options
options.planitID = Planit.randomString(20) unless options.planitID
if options.color then color = options.color else color = '#FC5B3F'
# find position
left = ((parseFloat(options.coords[0]) / 100) * container.width()) - 15
top = ((parseFloat(options.coords[1]) / 100) * container.height()) - 15
# create the marker
markersContainer.append(
$('<div></div>')
.addClass(Planit.markerClass)
.attr
'data-marker': options.planitID
'data-xPc': options.coords[0]
'data-yPc': options.coords[1]
.css
left: "#{left}px"
top: "#{top}px"
backgroundColor: color
)
# find the marker
marker = markersContainer.find(".#{Planit.markerClass}").last()
markerObj = new Planit.Marker(plan, options.planitID)
# add content and styles if passed as options
if options.id
marker.attr('data-id': options.id)
if options.class
marker.addClass(options.class)
if options.html
marker.html(options.html)
if options.size
marker.css
width: "#{options.size}px"
height: "#{options.size}px"
# setup draggable if necessary
if options.draggable
marker.addClass('draggable')
markerObj.enableDragging(marker)
# bind marker events
if plan.options.markerMouseOver
marker.on 'mouseover', (e) =>
plan.options.markerMouseOver(e, markerObj)
true
if plan.options.markerMouseOut
marker.on 'mouseout', (e) =>
plan.options.markerMouseOut(e, markerObj)
true
# setup infobox if necessary
if options.infobox
id = Planit.randomString(16)
# set style options on infobox
infobox = options.infobox
if infobox.position then position = infobox.position else position = 'top'
if infobox.arrow then arrow = true else arrow = false
if arrow == true then arrowClass = 'arrow' else arrowClass = ''
classes = "#{Planit.infoboxClass} #{position} #{arrowClass}"
# add infobox
container.find(".#{Planit.infoboxContainerClass}").append """
<div class="#{classes}" id="info-#{id}"
data-position="#{position}">
#{infobox.html}
</div>
"""
# add post-options if necessary
if infobox.offsetX
container.find(".#{Planit.infoboxClass}").last().attr
'data-offset-x': infobox.offsetX
if infobox.offsetY
container.find(".#{Planit.infoboxClass}").last().attr
'data-offset-y': infobox.offsetY
marker.attr('data-infobox', "info-#{id}")
markerObj.positionInfobox()
markerObj
enableDragging: (marker) =>
marker.on 'mousedown', (e) =>
if e.which == 1
marker = $(e.target).closest(".#{Planit.markerClass}")
marker.addClass(Planit.draggingClass)
marker.attr
'data-drag-start-x': e.pageX
'data-drag-start-y': e.pageY
# ======================================================== Calculations
# Get the position of the marker as a percentage of 100,
# relative to the top left of the image (if there is an image).
#
position: =>
xPx = @marker.position().left + (@marker.outerWidth() / 2)
yPx = @marker.position().top + (@marker.outerHeight() / 2)
if @image
wImg = @image.width()
hImg = @image.height()
xImg = parseInt(@image.css('left'))
yImg = parseInt(@image.css('top'))
xPc = ((xPx + Math.abs(xImg)) / wImg) * 100
yPc = ((yPx + Math.abs(yImg)) / hImg) * 100
else
xPc = (xPx / @container.width()) * 100
yPc = (yPx / @container.height()) * 100
[xPc, yPc]
# Get the position of the marker as a percentage of 100,
# relative to the top left of the container.
#
relativePosition: =>
xPx = @marker.position().left + (@marker.outerWidth() / 2)
yPx = @marker.position().top + (@marker.outerHeight() / 2)
xPc = (xPx / @container.width()) * 100
yPc = (yPx / @container.height()) * 100
[xPc, yPc]
# ======================================================== Attributes
# The background color of the marker
#
color: =>
@marker.css('backgroundColor')
# Randomly-generated ID given by planit when the marker is
# added to the plan.
#
planitID: =>
@marker.attr('data-marker')
# The ID of the marker, which would have been a manual
# option
#
id: =>
@marker.attr('data-id')
# Whether or not the marker is allowed to be dragged
#
isDraggable: =>
@marker.hasClass('draggable')
# ======================================================== Infobox
# The jQuery object that is the markers infobox (if the
# marker has an infobox)
#
infobox: =>
infobox = @container.find("##{@marker.attr('data-infobox')}")
if infobox.length > 0 then infobox else null
# The markup within the infobox, if the marker has an
# infobox
#
infoboxHTML: =>
if @infobox() && @infobox().length > 0 then @infobox().html() else null
# Whether the infobox is being displayed.
#
infoboxVisible: =>
@infobox() && @infobox().hasClass('active')
# Hides the infobox if it is visible.
#
hideInfobox: =>
if @infoboxVisible()
@infobox().addClass('hidden')
true
else
false
# Shows the infobox if it is hidden.
#
showInfobox: =>
if @infobox() && !@infoboxVisible()
@infobox().addClass('active')
@unhideInfobox()
true
else
false
# Similar to showInfobox, but less agressive. It takes
# away its hidden class, instead of adding an active
# class.
#
unhideInfobox: =>
if @infoboxVisible()
@infobox().removeClass('hidden')
true
else
false
# Find the appropriate coordinates at which to display the
# infobox, based on options.
#
infoboxCoords: =>
infobox = @container.find("##{@marker.attr('data-infobox')}")
markerCenterX = (parseFloat(@relativePosition()[0] / 100) * @container.width())
markerCenterY = (parseFloat(@relativePosition()[1] / 100) * @container.height())
iWidth = infobox.outerWidth()
iHalfWidth = iWidth / 2
iHeight = infobox.outerHeight()
iHalfHeight = iHeight / 2
cWidth = @container.width()
cHeight = @container.height()
mWidth = @marker.outerWidth()
mHalfWidth = mWidth / 2
mHeight = @marker.outerHeight()
mHalfHeight = mHeight / 2
buffer = 5
offsetX = parseInt(infobox.attr('data-offset-x'))
offsetX = 0 unless offsetX
offsetY = parseInt(infobox.attr('data-offset-y'))
offsetY = 0 unless offsetY
switch infobox.attr('data-position')
when 'top'
infoLeft = markerCenterX - iHalfWidth
infoTop = markerCenterY - iHeight - mHalfHeight - buffer
when 'right'
infoLeft = markerCenterX + mHalfWidth + buffer
infoTop = markerCenterY - iHalfHeight
when 'bottom'
infoLeft = markerCenterX - iHalfWidth
infoTop = markerCenterY + mHalfHeight + buffer
when 'left'
infoLeft = markerCenterX - iWidth - mHalfWidth - buffer
infoTop = markerCenterY - iHalfHeight
when 'top-left'
infoLeft = markerCenterX - iWidth - mHalfWidth + buffer
infoTop = markerCenterY - iHeight - mHalfHeight + buffer
when 'top-right'
infoLeft = markerCenterX + mHalfWidth - buffer
infoTop = markerCenterY - iHeight - mHalfHeight + buffer
when 'bottom-left'
infoLeft = markerCenterX - iWidth - mHalfWidth + buffer
infoTop = markerCenterY + mHalfHeight - buffer
when 'bottom-right'
infoLeft = markerCenterX + mHalfWidth - buffer
infoTop = markerCenterY + mHalfHeight - buffer
left: infoLeft + offsetX
top: infoTop + offsetY
# Places the infobox in the correct position.
#
positionInfobox: =>
coords = @infoboxCoords()
@container.find("##{@marker.attr('data-infobox')}").css
left: "#{coords.left}px"
top: "#{coords.top}px"
@position()
# Animates the infobox from its current position to its
# new position.
#
animateInfobox: =>
coords = @infoboxCoords()
@container.find("##{@marker.attr('data-infobox')}").animate
left: "#{coords.left}px"
top: "#{coords.top}px"
, 250, () =>
return @position()
# ======================================================== Actions
# positions the marker and infobox based on its data
# attributes
#
set: =>
if @image
left = (@image.width() * (@marker.attr('data-xPc') / 100)) +
parseFloat(@image.css('left')) - (@marker.outerWidth() / 2)
top = (@image.height() * (@marker.attr('data-yPc') / 100)) +
parseFloat(@image.css('top')) - (@marker.outerHeight() / 2)
else
left = (@container.width() * (@marker.attr('data-xPc') / 100)) -
(@marker.outerWidth() / 2)
top = (@container.height() * (@marker.attr('data-yPc') / 100)) -
(@marker.outerHeight() / 2)
@marker.css
left: "#{left}px"
top: "#{top}px"
@positionInfobox()
[left, top]
# Updates the marker's data attributes with its new
# position.
#
savePosition: =>
coords = @position()
@marker.attr
'data-xPc': coords[0]
'data-yPc': coords[1]
coords
# Allows you to change the attributes of the marker on the
# fly.
#
update: (options) =>
if options.color
@marker.css(backgroundColor: options.color)
if options.infobox
@marker.find(".#{Planit.infoboxClass}").html(options.infobox)
@positionInfobox()
if options.draggable != undefined
if options.draggable == true
@marker.addClass('draggable')
@.enableDragging(@marker)
else
@marker.removeClass('draggable')
@marker.off('mousedown')
if options.coords
left = ((parseFloat(options.coords[0]) / 100) * @container.width()) - 15
top = ((parseFloat(options.coords[1]) / 100) * @container.height()) - 15
@marker.css
left: "#{left}px"
top: "#{top}px"
true
# Removes the marker from the plan.
#
remove: =>
@infobox().remove() if @infobox()
@marker.remove()
true