Menu.coffee 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. class Menu
  2. constructor: ->
  3. @visible = false
  4. @items = []
  5. @node = null
  6. @height = 0
  7. @direction = "bottom"
  8. show: =>
  9. window.visible_menu?.hide()
  10. @visible = true
  11. window.visible_menu = @
  12. @direction = @getDirection()
  13. hide: =>
  14. @visible = false
  15. toggle: =>
  16. if @visible
  17. @hide()
  18. else
  19. @show()
  20. Page.projector.scheduleRender()
  21. addItem: (title, cb, selected=false) ->
  22. @items.push([title, cb, selected])
  23. storeNode: (node) =>
  24. @node = node
  25. # Animate visible
  26. if @visible
  27. node.className = node.className.replace("visible", "")
  28. setTimeout (=>
  29. node.className += " visible"
  30. node.attributes.style.value = @getStyle()
  31. ), 20
  32. node.style.maxHeight = "none"
  33. @height = node.offsetHeight
  34. node.style.maxHeight = "0px"
  35. @direction = @getDirection()
  36. getDirection: =>
  37. if @node and @node.parentNode.getBoundingClientRect().top + @height + 60 > document.body.clientHeight and @node.parentNode.getBoundingClientRect().top - @height > 0
  38. return "top"
  39. else
  40. return "bottom"
  41. handleClick: (e) =>
  42. keep_menu = false
  43. for item in @items
  44. [title, cb, selected] = item
  45. if title == e.currentTarget.textContent or e.currentTarget["data-title"] == title
  46. keep_menu = cb?(item)
  47. break
  48. if keep_menu != true and cb != null
  49. @hide()
  50. return false
  51. renderItem: (item) =>
  52. [title, cb, selected] = item
  53. if typeof(selected) == "function"
  54. selected = selected()
  55. if title == "---"
  56. return h("div.menu-item-separator", {key: Time.timestamp()})
  57. else
  58. if cb == null
  59. href = undefined
  60. onclick = @handleClick
  61. else if typeof(cb) == "string" # Url
  62. href = cb
  63. onclick = true
  64. else # Callback
  65. href = "#"+title
  66. onclick = @handleClick
  67. classes = {
  68. "selected": selected,
  69. "noaction": (cb == null)
  70. }
  71. return h("a.menu-item", {href: href, onclick: onclick, "data-title": title, key: title, classes: classes}, title)
  72. getStyle: =>
  73. if @visible
  74. max_height = @height
  75. else
  76. max_height = 0
  77. style = "max-height: #{max_height}px"
  78. if @direction == "top"
  79. style += ";margin-top: #{0 - @height - 50}px"
  80. else
  81. style += ";margin-top: 0px"
  82. return style
  83. render: (class_name="") =>
  84. if @visible or @node
  85. h("div.menu#{class_name}", {classes: {"visible": @visible}, style: @getStyle(), afterCreate: @storeNode}, @items.map(@renderItem))
  86. window.Menu = Menu
  87. # Hide menu on outside click
  88. document.body.addEventListener "mouseup", (e) ->
  89. if not window.visible_menu or not window.visible_menu.node
  90. return false
  91. menu_node = window.visible_menu.node
  92. menu_parents = [menu_node, menu_node.parentNode]
  93. if e.target.parentNode not in menu_parents and e.target.parentNode.parentNode not in menu_parents
  94. window.visible_menu.hide()
  95. Page.projector.scheduleRender()