summaryrefslogblamecommitdiff
path: root/examples/demos/Controller.scala
blob: 81bcdb4831e521c018c3f68e6a11f686cbfdbc31 (plain) (tree)
1
2
3
4
5
6
7
8
9






                                           

                                                   





                             







                                                                         

                                                       

                                               



                                          
                                                                           
 


                                                
 



                                                                      

                                                                           

                       
                                                        





                                            
                                                


                        
                                                           
       






                                                                       
     
 




                                                    
                           



                                 






                                                                                         
                                                       







                                                       
     
 
                                                      




                                               
     

   




                                                                      

                                      

                                                       
 









                                                        
 






                                                   

                                                              
         













                                                                     











                                                       
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._

case class Tree[T](name: T, children: Seq[Tree[T]])
@JSExport
object Controller{

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

    val structure = upickle.readJs[Tree[String]](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 contentTree = {
      def rec(current: Tree[String], depth: Int): Tree[dom.HTMLElement] = {
        val myCls =
          "menu-item" +
          (if (depth <= 1) " menu-item-divided" else "")

        val frag =
          li(
            a(
              current.name,
              href:="#"+munge(current.name),
              paddingLeft := s"${depth * 15}px",
              cls:=myCls
            )
          ).render
        Tree(frag, current.children.map(rec(_, depth + 1)))
      }
      rec(structure, 0)
    }
    val contentList = {
      def rec(current: Tree[dom.HTMLElement]): Seq[dom.HTMLElement] = {
        current.name +: current.children.flatMap(rec)
      }
      rec(contentTree).toVector
    }

    val frag = div(cls:="pure-menu pure-menu-open")(
      a(cls:="pure-menu-heading", href:="#")(
        "Contents"
      ),
      ul(cls:="menu-item-list")(
        contentList.drop(1)
      )
    )
    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: Tree[String]): 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, contentList, contentTree)

    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],
                contentList: Seq[dom.HTMLElement],
                contentTree: Tree[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(contentList(index))) {
          contentList(index).scrollIntoView(lastIndex > index)
        }
        def rec(curr: Tree[dom.HTMLElement]): Boolean = {
          if (curr.children.map(rec).contains(true) ||
              curr.name == contentList(index)){
            addClass(curr.name, "pure-menu-selected")
            curr.children.map(_.name).foreach(removeClass(_, "hide"))
            true
          }else{
            removeClass(curr.name, "pure-menu-selected")
            addClass(curr.name, "hide")
            false
          }
        }
        rec(contentTree)

        lastIndex = index
      }
    }
    run()
    main.onscroll = (e: dom.UIEvent) => {
      if (!scrolling){
        scrolling = true
        dom.requestAnimationFrame((d: Double) => run())
      }
    }
  }
}