summaryrefslogtreecommitdiff
path: root/examples/demos/Controller.scala
blob: c32c293bfcc0fb850d91a3694bd9eb0f2531a5f3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import scala.scalajs.js
import scala.scalajs.js.annotation.JSExport
import org.scalajs.dom
import org.scalajs.dom.extensions._
import scala.collection.mutable
import scalatags.JsDom.all._
import rx._
import util.{Success, Failure}
case class Node(name: String, children: mutable.Buffer[Node])
@JSExport
object Controller{

  def munge(name: String) = {
    name.replace(" ", "")
  }
  def toggleClass(el: dom.HTMLElement, cls: String) = {
    val frags = el.className.split(' ')
    if (!frags.contains(cls)) el.className = el.className + " " + cls
    else el.className = el.className.split(' ').filter(_ != cls).mkString(" ")
  }
  @JSExport
  def main(data: scala.scalajs.js.Any) = {

    val structure = upickle.readJs[Node](upickle.json.readJs(data))

    val Seq(main, menu, layout, menuLink) = Seq(
      "main", "menu", "layout", "menuLink"
    ).map(dom.document.getElementById)

    val snippets = dom.document.getElementsByClassName("highlight-me")

    snippets.foreach(js.Dynamic.global.hljs.highlightBlock(_))

    val contentBar = {
      def rec(current: Node, depth: Int): Seq[dom.HTMLLIElement] = {
        println("\t"*depth + current.name)
        val myCls =
          "menu-item" +
          (if (depth == 1) " menu-item-divided" else "")

        val frag =
          li(
            a(
              current.name,
              href:="#"+munge(current.name),
              paddingLeft := s"${depth * 10 + 10}px",
              cls:=myCls
            )
          ).render

        frag +: current.children.flatMap(rec(_, depth + 1))
      }
      structure.children.flatMap(rec(_, 0))
    }

    val frag = div(cls:="pure-menu pure-menu-open")(
      a(cls:="pure-menu-heading", href:="#")(
        "Contents"
      ),
      ul(cls:="menu-item-list")(
        contentBar
      )
    )
    menu.appendChild(frag.render)


    val headers = {
      def offset(el: dom.HTMLElement, parent: dom.HTMLElement): Double = {
        if (el == parent) 0
        else el.offsetTop + offset(el.offsetParent.asInstanceOf[dom.HTMLElement], parent)
      }
      val menuItems = {
        def rec(current: Node): Seq[String] = {
          current.name +: current.children.flatMap(rec)
        }
        rec(structure).tail
      }
      menuItems.map(munge)
        .map(dom.document.getElementById)
        .map(offset(_, main))
        .toArray
    }

    scrollSpy(main, headers, contentBar)

    menuLink.onclick = (e: dom.MouseEvent) => {
      toggleClass(layout, "active")
      toggleClass(menu, "active")
      toggleClass(menuLink, "active")
    }
  }

  /**
   * Needs to be done in a sketchy imperative fashion for performance:
   * onscroll gets called quite a lot, so any additional work makes it
   * noticeable jerky
   */
  def scrollSpy(main: dom.HTMLElement,
                headers: Seq[Double],
                contentBar: Seq[dom.HTMLElement]) = {

    def isElementInViewport(el: dom.HTMLElement) = {
      val rect = el.getBoundingClientRect()
      rect.top >= 0 && rect.bottom <= dom.innerHeight
    }

    var scrolling = false
    var lastIndex = -1
    def run() = {
      scrolling = false
      val threshold = main.scrollTop + main.clientHeight
      var index = 0
      while(index < headers.length && index >= 0){
        index += 1
        if (headers(index) > threshold) index *= -1
      }
      index = -index - 1
      if (index != lastIndex){
        if (!isElementInViewport(contentBar(index))) {
          contentBar(index).scrollIntoView(lastIndex > index)
        }
        if (lastIndex != -1)
          toggleClass(contentBar(lastIndex), "pure-menu-selected")
        toggleClass(contentBar(index), "pure-menu-selected")
        lastIndex = index
      }
    }
    run()
    main.onscroll = (e: dom.UIEvent) => {
      if (!scrolling){
        scrolling = true
        dom.requestAnimationFrame((d: Double) => run())
      }
    }
  }
}