Skip to content

Instantly share code, notes, and snippets.

@bbarker
Created June 1, 2018 11:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bbarker/0014690c261b80c00c62d75e3f51744b to your computer and use it in GitHub Desktop.
Save bbarker/0014690c261b80c00c62d75e3f51744b to your computer and use it in GitHub Desktop.
Simple Router in monadic-html
import mhtml.{Rx, Var}
import scala.xml.Node
import Router._
import org.scalajs.dom
import org.scalajs.dom.Event
import edu.ncrn.cornell.site.view.utils.Utils._
import scala.collection.mutable.WrappedArray
/**
* A recursive, component-level router. In general each component should define its own route
* if a route is needed at all.
*/
case class Router(remainingPath: Rx[String], route: Rx[String] => Node) {
val view: Node = route(remainingPath)
}
object Router {
val routePath: Rx[String] = Rx(dom.window.location.hash).merge{
val updatedHash = Var(dom.window.location.hash)
dom.window.onhashchange = (ev: Event) => {
updatedHash := dom.window.location.hash
}
updatedHash.map(hash => hash.replaceFirst("#/", ""))
}
def pathUptoIdx(pathParts: Iterable[String], idx: Int): String =
"#/" + pathParts.take(idx + 1).mkString("/")
val breadCrumbs: Rx[Node] = routePath.map { routeHash =>
val ignoreInitCrumbs = Set("", "app", "about")
val pathSplit: WrappedArray[String] = routeHash.split('/')
val crumbs: WrappedArray[String] = pathSplit.headOption match {
case Some(cr) =>
if (ignoreInitCrumbs.contains(cr)) pathSplit.drop(1)
else pathSplit
case None => pathSplit
}
val (otherCrumbs, thisCrumb) = crumbs.splitAt(crumbs.length-1)
if (crumbs.nonEmpty) {
<ol class="breadcrumb">
{otherCrumbs.zipWithIndex.mapToNode { case (cr: String, idx: Int) =>
<li class="breadcrumb-item">
<a href={pathUptoIdx(otherCrumbs, idx)}>{ cr }</a>
</li>
}}
{thisCrumb.mapToNode(cr => <li class="breadcrumb-item active">{ cr }</li>)}
</ol>
}
else <div></div>
}
//TODO: handle case where not splittable
/**
* Utiltiy to split route
* @param routeInRx
* @return (current route, child route)
*/
def splitRoute(routeInRx: Rx[String]): (Rx[String], Rx[String]) = {
val splitRx = routeInRx.map { routeIn =>
val (fst, snd) =
if (routeIn.contains("/"))
routeIn.splitAt(routeIn.indexOfSlice("/"))
else (routeIn, "")
(fst, snd.replaceFirst("/", ""))
}
(splitRx.map(tup => tup._1), splitRx.map(tup => tup._2))
}
}
@bbarker
Copy link
Author

bbarker commented Jun 1, 2018

A top-level example:

    private def thisRoute(path: Rx[String]): Node = {
      val (curPathRx, childPathRx) = Router.splitRoute(path)
      lazy val codebookList = CodebookList(childPathRx)
      lazy val varList = VariableList(None, childPathRx)
      curPathRx.flatMap{curPath =>
        if (curPath == Routes.about) Rx(aboutComp.view())
        else if (curPath == Routes.codebook) Rx(codebookList.view())
        else if (curPath == Routes.variable) Rx(varList.view())
        else if (curPath == Routes.config) Rx(configComp.view())
        else {
          HostConfig.currentApiUri.flatMap{curUri =>
            println(s"main page's currentApiSource is ${curUri}") // DEBUG
            setTimeout(5 * 1000) {

            }
            val goodApiUriMaybe = HostConfig.checkApiUri(curUri.toString)
            goodApiUriMaybe.map {
              case _: Some[URI] =>
                dom.window.location.hash = "#/" + Routes.codebook
              case None =>
                //TODO: not working, need to change currentApiUri to use Diode's Pot
                dom.window.location.hash = "#/" + Routes.config
            }.map(_ => <div>Unreached</div>)
          }
        }
      }.toNode()
    }
    val router = Router(Router.routePath, thisRoute)

Another example:

    def thisRoute(path: Rx[String]): Node = {
      val (curPathRx, childPathRx) = Router.splitRoute(path)
      lazy val varList = VariableList(Option(handle), childPathRx)
      val nodeRx: Rx[Node] = curPathRx.map{curPath: String =>
        if (curPath == "") view(details, handle)
        else varList.view()
        //TODO add check on codebook handle above?
        //else Rx(<div>Make An Error page</div>)
      }
      nodeRx.toNode()
    }
    val router = Router(route, thisRoute)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment