summaryrefslogtreecommitdiff
path: root/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala
diff options
context:
space:
mode:
Diffstat (limited to 'examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala')
-rw-r--r--examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala74
1 files changed, 66 insertions, 8 deletions
diff --git a/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala b/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala
index 9c0fbd2..7b238f4 100644
--- a/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala
+++ b/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala
@@ -2,6 +2,8 @@ package scrollmenu
import org.scalajs.dom
+import scalatags.JsDom.all._
+
case class Tree[T](value: T, children: Vector[Tree[T]])
case class MenuNode(frag: dom.HTMLElement, start: Int, end: Int)
@@ -10,18 +12,69 @@ case class MenuNode(frag: dom.HTMLElement, start: Int, end: Int)
* High performance scrollspy to work keep the left menu bar in sync.
* Lots of sketchy imperative code in order to maximize performance.
*/
-class ScrollSpy(headers: Vector[Double], domTrees: Seq[Tree[MenuNode]]){
- var scrolling = false
- def apply(threshold: Double) = if (!scrolling){
+class ScrollSpy(structure: Tree[String], main: dom.HTMLElement){
+ val (headers, domTrees) = {
+ var i = 0
+ def recurse(t: Tree[String], depth: Int): Tree[MenuNode] = {
+ val curr =
+ li(
+ a(
+ t.value,
+ href:="#"+Controller.munge(t.value),
+ cls:="menu-item"
+ )
+ )
+ val originalI = i
+ i += 1
+ val children = t.children.map(recurse(_, depth + 1))
+ Tree(
+ MenuNode(
+ curr(ul(paddingLeft := "15px",children.map(_.value.frag))).render,
+ originalI,
+ if (children.length > 0) children.map(_.value.end).max else originalI + 1
+ ),
+ children
+ )
+ }
+ def offset(el: dom.HTMLElement, parent: dom.HTMLElement): Double = {
+ if (el == parent) 0
+ else el.offsetTop + offset(el.offsetParent.asInstanceOf[dom.HTMLElement], parent)
+ }
+ val headers = {
+ val menuItems = {
+ def rec(current: Tree[String]): Seq[String] = {
+ current.value +: current.children.flatMap(rec)
+ }
+ rec(structure).tail
+ }
+ menuItems.map(Controller.munge)
+ .map(dom.document.getElementById)
+ .map(offset(_, main))
+ .toVector
+ }
+ val domTrees = structure.children.map(recurse(_, 0))
+ (headers, domTrees)
+ }
+
+
+ private[this] var scrolling = false
+ def apply(threshold: => Double) = if (!scrolling){
scrolling = true
- dom.requestAnimationFrame((d: Double) => start(threshold))
+ dom.setTimeout(() => start(threshold), 200)
}
- def start(threshold: Double) = {
+ private[this] def start(threshold: Double) = {
scrolling = false
+ def scroll(el: dom.HTMLElement) = {
+ val rect = el.getBoundingClientRect()
+ if (rect.top <= 0)
+ el.scrollIntoView(true)
+ else if (rect.bottom > dom.innerHeight)
+ el.scrollIntoView(false)
+ }
def walkTree(tree: Tree[MenuNode]): Boolean = {
- val Tree(MenuNode(menuItem, index, next), children) = tree
- val before = headers(index) < threshold
- val after = (next >= headers.length) || headers(next) > threshold
+ val Tree(MenuNode(menuItem, start, end), children) = tree
+ val before = headers(start) < threshold
+ val after = (end >= headers.length) || headers(end) > threshold
val win = before && after
if (win){
menuItem.classList.remove("hide")
@@ -34,7 +87,10 @@ class ScrollSpy(headers: Vector[Double], domTrees: Seq[Tree[MenuNode]]){
winFound = winFound | newWinFound
}
if (!winFound) {
+ // This means it's the leaf element, because it won but there
+ // aren't any children which won, so it must be the actual leaf
tree.children.foreach(_.value.frag.classList.remove("selected"))
+ scroll(menuItem)
}
menuItem.children(0).classList.add("pure-menu-selected")
}else{
@@ -46,4 +102,6 @@ class ScrollSpy(headers: Vector[Double], domTrees: Seq[Tree[MenuNode]]){
}
domTrees.map(walkTree)
}
+
+
} \ No newline at end of file