Skip to content

Instantly share code, notes, and snippets.

@rlivsey
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rlivsey/f9c1ef500d37cdaf97cd to your computer and use it in GitHub Desktop.
Save rlivsey/f9c1ef500d37cdaf97cd to your computer and use it in GitHub Desktop.
An Ember pop-over component
`import {getScrollParent} from 'app/lib/dom'`
alias = Ember.computed.alias
PopOverComponent = Ember.Component.extend
classNames: "pop-over"
classNameBindings: ["position", "isShowing"]
isShowing: false
position: "bottom"
actions:
toggle: -> if @get("isShowing") then @hide() else @show()
show: -> @show()
hide: -> @hide()
teardownEventListeners: (->
return unless @_hideHandler
@$(document).off(".popover", @_hideHandler)
).on("willDestroyElement")
setupEventListeners: ->
@_hideHandler ||= (e) =>
@hide() unless $(e?.target).closest(".pop-over").length
@$(document).trigger("popover-opened")
@$(document).on("mouseup.popover", @_hideHandler)
@$(document).on("popover-opened.popover", @_hideHandler)
setup: (->
if @get("isShowing") then @show() else @hide()
).on("didInsertElement")
hide: ->
@set("isShowing", false)
@teardownEventListeners()
show: ->
@set("isShowing", true)
@setupEventListeners()
PopOverAnchorView = Ember.View.extend Ember.TargetActionSupport,
classNames: "pop-over-anchor"
target: alias('parentView')
click: (e) ->
e.preventDefault()
e.stopImmediatePropagation()
@triggerAction(action: 'toggle')
PopOverBodyView = Ember.View.extend
classNames: "pop-over-body-wrapper"
layout: Ember.Handlebars.compile """
{{#if view.isShowing}}
<div class="pop-over-body">
<div class="pop-over-arrow"></div>
{{yield}}
</div>
{{/if}}
"""
isShowing: alias("parentView.isShowing")
position: alias("parentView.position")
showingChanged: (->
if @get("isShowing")
Em.run.scheduleOnce('afterRender', this, 'reposition')
@listenToScroll()
else
@stopListeningToScroll()
).observes("isShowing").on("didInsertElement")
listenToScroll: ->
@_scrollHandler ||= => Ember.run.debounce(this, @reposition, 10);
getScrollParent(@$()).on "scroll", @_scrollHandler
stopListeningToScroll: (->
getScrollParent(@$()).off "scroll", @_scrollHandler
).on("willDestroyElement")
anchorPosition: ->
$parent = @get("parentView").$()
height = $parent.outerHeight()
width = $parent.outerWidth()
{top, left} = $parent.offset()
bottom = top + height
right = left + width
{ left, right, top, bottom, height, width }
reposition: ->
# we're done in debounced / next runloop
# so it's possible that we've been torn down before it's had a chance to run
return unless @_state == "inDOM"
anchor = @anchorPosition()
return unless anchor
height = @$().outerHeight(true)
width = @$().outerWidth(true)
edgeBuffer = 10
# TODO - DRY this up
switch @get("position")
when "right", "left"
left = anchor.right
maxTop = $(window).height() - height - edgeBuffer
maxLeft = $(window).width() / 2
idealTop = anchor.top + (anchor.height / 2) - (height / 2)
top = if idealTop > maxTop then maxTop else idealTop
arrowDiff = idealTop - top
@$(".pop-over-arrow").css({bottom: (height / 2) - arrowDiff})
# past 50% of window width, flip it
if left > maxLeft
@set "position", "left"
move = {top, left: "auto", bottom: "auto", right: $(window).width() - anchor.left}
else
@set "position", "right"
move = {top, left, bottom: "auto", right: "auto"}
when "bottom", "top"
top = anchor.bottom
maxTop = $(window).height() / 2
maxLeft = $(window).width() - width - edgeBuffer
idealLeft= anchor.left + (anchor.width / 2) - (width / 2)
left = if idealLeft > maxLeft then maxLeft else idealLeft
arrowDiff = idealLeft - left
@$(".pop-over-arrow").css({left: (width / 2) + arrowDiff})
# past 50% of window height, flip it
if top > maxTop
@set "position", "top"
move = {top: "auto", left, bottom: $(window).height() - anchor.top, right: "auto"}
else
@set "position", "bottom"
move = {top, left, bottom: "auto", right: "auto"}
@$().css(move)
Ember.Handlebars.helper "pop-over-anchor", PopOverAnchorView
Ember.Handlebars.helper "pop-over-body", PopOverBodyView
`export {
PopOverAnchorView,
PopOverBodyView,
PopOverComponent
}`
`export default PopOverComponent`
{{#pop-over}}
{{#pop-over-anchor}}
Click me
{{/pop-over-anchor}}
{{#pop-over-body}}
<ul>
<li {{action doSomething}}>One</li>
<li {{action doSomethingElse}}>two</li>
</ul>
{{/pop-over-body}}
{{/pop-over}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment