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






                                           
 









                                                                                   





                             
                                                    





                                                                         

                                                       

                                               



                                          
                                                                           
 


                                                
 



                                                                      

                                                                           

                       
                                                        


                  
                                              


                                            


                        
                                                           
       






                                                                       
     
 




                                                    
                           



                                 






                                                                                         
                                                       






                                                       
                 
     
 
                                                      




                                               
     

   




                                                                      
                                      

                                                     
                                                       
 
 





                                                        
 




                                                   
                    
 

                                                                 










                                                       































































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

object Renderer{
  import japgolly.scalajs.react._ // React
  import vdom.ReactVDom._         // Scalatags → React virtual DOM
  import vdom.ReactVDom.all._     // Scalatags html & css (div, h1, textarea, etc.)

  val Menu = ReactComponentB[Int]("Menu").render{ p =>

  }.build
}
case class Tree[T](name: T, children: Vector[Tree[T]])
@JSExport
object Controller{

  def munge(name: String) = {
    name.replace(" ", "")
  }
  def addClass(el: dom.HTMLElement, cls: String) = {
    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(
            paddingLeft := s"${depth * 15}px",
            a(
              current.name,
              href:="#"+munge(current.name),
              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))
        .toVector
    }

    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: Vector[Double],
                contentList: Vector[dom.HTMLElement],
                contentTree: Tree[dom.HTMLElement]) = {



    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

      if (index != lastIndex){
        updateSideBar(lastIndex, index, contentList, contentTree)
        lastIndex = index
      }
    }
    run()
    main.onscroll = (e: dom.UIEvent) => {
      if (!scrolling){
        scrolling = true
        dom.requestAnimationFrame((d: Double) => run())
      }
    }
  }
  def isElementInViewport(el: dom.HTMLElement) = {
    val rect = el.getBoundingClientRect()
    rect.top >= 0 && rect.bottom <= dom.innerHeight
  }
  val lastShown = new js.Array[dom.HTMLElement](0)
  val lastLined = new js.Array[dom.HTMLElement](0)
  def updateSideBar(lastIndex: Int,
                    index: Int,
                    contentList: Vector[dom.HTMLElement],
                    contentTree: Tree[dom.HTMLElement]) = {

    println(s"MOVING $lastIndex -> $index")
    if (!isElementInViewport(contentList(index))) {
      contentList(index).scrollIntoView(lastIndex > index)
    }
    val shown = new js.Array[dom.HTMLElement](0)
    val lined = new js.Array[dom.HTMLElement](0)
    /**
     * Makes two passes over the children list; once to determine if
     * the current element is a parent of the current header, and another
     * to mark all the children of the current element with the correct
     * CSS classes.
     */
    def rec(curr: Tree[dom.HTMLElement]): Boolean = {

      var found = false
      var i = 0
      var j = 0
      while (j < curr.children.length){
        val x = curr.children(j)
        j+= 1
        val f = rec(x)
        found |= f
        if (!found) i += 1
      }

      if (found || curr.name == contentList(index)){
        var j = 0
        while (j < curr.children.length){
          val x = curr.children(j)
          if (found && i > 0){
            lined.push(x.name)
            i -= 1
          }

          j+= 1
          shown.push(x.name)
        }
        lined.push(curr.name)
        true
      }else false

    }
    rec(contentTree)
    for(el <- contentList){
      if (shown.indexOf(el) != -1) removeClass(el, "hide")
      else addClass(el, "hide")

      if (lined.indexOf(el) == -1) removeClass(el, "lined")
      else addClass(el, "lined")
    }
    if (lastIndex != -1) removeClass(contentList(lastIndex), "pure-menu-selected")
    addClass(contentList(index), "pure-menu-selected")
  }
}