All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.liftweb.sitemap.Loc.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2007-2011 WorldWide Conferencing, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.liftweb
package sitemap

import net.liftweb.http._
import net.liftweb.common._
import net.liftweb.util._
import Helpers._
import auth._

import scala.xml.{NodeSeq, Text}

/**
 * A menu location
 */
trait Loc[T] {
  def name: String

  def link: Loc.Link[T]

  def text: Loc.LinkText[T]

  def overrideValue: Box[T] = Empty

  object requestValue extends RequestVar[Box[T]](defaultRequestValue) {
    override val __nameSalt = randomString(10)
  }

  /**
   * Override this if the default request value should
   * be something other than Empty
   */
  protected def defaultRequestValue: Box[T] = Empty

  /**
   * When the menu item is displayed, what CSS class do we add to the
   * node?
   */
  def cssClassForMenuItem: Box[String] = cacheCssClassForMenuItem.map(_())

  /**
   * By default, this lazy val looks for the MenuCssClass LocParam and
   * uses it.
   */
  protected lazy val cacheCssClassForMenuItem: Box[() => String] =
    allParams.flatMap {
      case a: Loc.MenuCssClass => List(a)
      case _ => Nil
    }.headOption.map(_.cssClass.func)

  /**
   * Given a value calculate the HREF to this item
   */
  def calcHref(in: T): String = appendQueryParameters(link.createPath(in),
                                                      Full(in))


  /**
   * Calculate HREF to this item using currentValue
   */
  def calcDefaultHref: String =  currentValue.map(p => link.createPath(p)).toOption.
    map(path => appendQueryParameters(path, currentValue)).getOrElse("")


  def defaultValue: Box[T]

  /**
  * The value of the Loc based on params (either Loc.Value or Loc.CalcValue)
  */
  def paramValue: Box[T] = calcValue.flatMap(f => f()) or staticValue

  private lazy val staticValue: Box[T] = {
    allParams.collectFirst {
      case Loc.Value(v) =>
        v.asInstanceOf[T]
    }
  }

  private lazy val calcValue: Box[() => Box[T]] = {
    params.collectFirst {
      case Loc.CalcValue(f: Function0[_]) =>
        f.asInstanceOf[()=>Box[T]]
    }
  }

  /**
   * Calculate the Query parameters
   */
  def queryParameters(what: Box[T]): List[(String, String)] =
    addlQueryParams.flatMap(_()) :::
  calcQueryParams.flatMap(_(what))

  protected def appendQueryParams(what: T)(nodeSeq: NodeSeq): NodeSeq =
    Text(appendQueryParameters(nodeSeq.text, Full(what)))

  protected def appendQueryParameters(in: String, what: Box[T]) =
    Helpers.appendQueryParameters(in, queryParameters(what))

  private lazy val addlQueryParams: List[() => List[(String, String)]] =
    params.collect{case lp: Loc.QueryParameters => lp.f}


  private lazy val calcQueryParams: List[Box[T] => List[(String, String)]] =
    params.collect{case lp: Loc.LocQueryParameters[T] => lp.f}

  /**
  * The current value of the cell: overrideValue or requestValue.is or defaultValue oe paramValue
  */
  def currentValue: Box[T] = overrideValue or requestValue.is or defaultValue or paramValue

  def childValues: List[T] = Nil

  def params: List[Loc.LocParam[T]]

  def allParams: List[Loc.AnyLocParam] = {
    if (_menu == null) {
      // Params are impossible without a menu
      Nil
    } else {
      (params.asInstanceOf[List[Loc.AnyLocParam]]) :::
       parentParams :::
       siteMap.globalParams
    }
  }

  private def parentParams: List[Loc.AnyLocParam] =
    _menu match {
      case null => Nil
      case menu => menu._parent match {
        case Full(parentMenu: Menu) =>
          if (!params.collect{case i: Loc.UseParentParams => true}.isEmpty) {
            parentMenu.loc.allParams.asInstanceOf[List[Loc.LocParam[Any]]]
          } else {
            Nil
          }
        case _ => Nil
      }
    }

  private lazy val _placeHolder_? = allParams.contains(Loc.PlaceHolder)

  def placeHolder_? : Boolean = _placeHolder_?

  private lazy val _hideIfNoKids_? = allParams.contains(Loc.HideIfNoKids)

  def hideIfNoKids_? = placeHolder_? || _hideIfNoKids_?

  // This implementation is less than ideal, but changing the type would
  // have been a breaking API change. This at least avoids this method
  // throwing an NPE
  def siteMap: SiteMap = Option(_menu).map(_.siteMap).getOrElse(null)

  def createDefaultLink: Option[NodeSeq] =
    currentValue.flatMap(p => link.createLink(p)).toOption.
    map(ns => Text(appendQueryParameters(ns.text, currentValue)))


  def createLink(in: T): Option[NodeSeq] = link.createLink(in).toOption.
  map(ns => Text(appendQueryParameters(ns.text, Full(in))))

  override def toString = "Loc("+name+", "+link+", "+text+", "+params+")"

  type LocRewrite =  Box[PartialFunction[RewriteRequest, (RewriteResponse, Box[T])]]

  def rewrite: LocRewrite = Empty

  def rewritePF: Box[LiftRules.RewritePF] = rewrite.map(
    rw =>
    new NamedPartialFunction[RewriteRequest, RewriteResponse] {
      def functionName = rw match {
        case rw: NamedPartialFunction[_, _] => rw.functionName
        case _ => "Unnamed"
      }

      def isDefinedAt(in: RewriteRequest) = rw.isDefinedAt(in)

      def apply(in: RewriteRequest): RewriteResponse = {
        val (ret, param) = rw.apply(in)
        requestValue.set(param)
        ret
      }
    }
  )

  /**
   * A `PartialFunction` that maps a snippet name, and an optional `Loc` value, in a `Tuple2`,
   * to a snippet function (`NodeSeq` => `NodeSeq`).
   */
  type SnippetTest = PartialFunction[(String, Box[T]), NodeSeq => NodeSeq]

  /**
   * The snippets defined by the `Loc` class itself, as opposed to those
   * provided by its `LocParams`.
   */
  def snippets: SnippetTest = Map.empty

  /**
   * Is the Loc marked as Stateless (this will force rendering of
   * the page into stateless mode)
   */
  def stateless_? : Boolean =
    if (Props.devMode) (calcStateless() || reqCalcStateless())
    else (_frozenStateless || reqCalcStateless())

  /**
   * A lazy val used to track statelessness for non-dev mode.
   * By default, it calls calcStateless().
   */
  protected lazy val _frozenStateless = calcStateless()

  /**
   * The method to calculate if this Loc is stateless.  By default
   * looks for the Loc.Stateless Param
   */
  protected def calcStateless(): Boolean = allParams.find {
    case Loc.Stateless => true
    case _ => false
  }.isDefined

  /**
   * Find the stateless calculation Loc params
   */
  protected def findStatelessCalc: (Box[Loc.CalcStateless], Box[Loc.CalcParamStateless[T]]) = (allParams.collect {
    case v @ Loc.CalcStateless(_) => v
  }.headOption,
    allParams.collect {
    case v @ Loc.CalcParamStateless(_) => v
  }.headOption)

  /**
   * The cached Loc params
   */
  protected lazy val _foundStatelessCalc: (Box[Loc.CalcStateless], Box[Loc.CalcParamStateless[T]]) = findStatelessCalc

  protected def foundStatelessCalc: (Box[Loc.CalcStateless], Box[Loc.CalcParamStateless[T]])  =
  if (Props.devMode) findStatelessCalc else _foundStatelessCalc

  /**
   * run the stateless calculation
   */
  protected def reqCalcStateless(): Boolean = {
    val (np, param) = foundStatelessCalc
    (np.map(_.f()) or param.map(_.f(currentValue))) openOr false
  }

  /**
   * The snippets provided by `LocParam`s
   */
  lazy val calcSnippets: SnippetTest =
    allParams
      .collect { case v: Loc.ValueSnippets[T] => v.snippets }
      .reduceLeftOption(_ orElse _)
      .getOrElse(Map.empty)

  /**
   * Look up a snippet by name, taking into account the current
   * `Loc` value.
   */
  def snippet(name: String): Box[NodeSeq => NodeSeq] = {
    snippets orElse calcSnippets lift (name, currentValue)
  }

  protected object accessTestRes extends RequestVar[Either[Boolean, Box[() => LiftResponse]]](_testAccess) {
    override val __nameSalt = randomString(10)
  }

  def testAccess: Either[Boolean, Box[() => LiftResponse]] = accessTestRes.is

  protected def _testAccess: Either[Boolean, Box[() => LiftResponse]] = {
    def testParams(what: List[Loc.LocParam[T]]): Either[Boolean, Box[() => LiftResponse]] = what match {
      case Nil => Left(true)

      case Loc.If(test, msg) :: xs => if (!test()) Right(Full(msg)) else testParams(xs)
      case Loc.IfValue(test, msg) :: xs => if (!test(currentValue)) Right(Full(msg)) else testParams(xs)

      case Loc.Unless(test, msg) :: xs => if (test()) Right(Full(msg)) else testParams(xs)
      case Loc.UnlessValue(test, msg) :: xs => if (test(currentValue)) Right(Full(msg)) else testParams(xs)

      case Loc.TestAccess(func) :: xs =>
        func() match {
          case Full(resp) => Right(Full(() => resp))
          case _ => testParams(xs)
        }

      case Loc.TestValueAccess(func) :: xs =>
        func(currentValue) match {
          case Full(resp) => Right(Full(() => resp))
          case _ => testParams(xs)
        }

      case x :: xs => testParams(xs)
    }

    testParams(allParams) match {
      case Left(true) => _menu.testParentAccess
      case x => x
    }
  }

  def earlyResponse: Box[LiftResponse] = {
    def early(what: List[Loc.LocParam[T]]): Box[LiftResponse] = what match {
      case Nil => Empty

      case Loc.EarlyResponse(func) :: xs =>
        func() match {
          case Full(r) => Full(r)
          case _ => early(xs)
        }

      case x :: xs => early(xs)
    }

    early(allParams)
  }

  /**
   * This method can be overridden to provide a template for this Loc
   */
  def calcTemplate: Box[NodeSeq] = Empty

  /**
   * The first Loc.Template or Loc.ValueTemplate in the param list.
   */
  def paramTemplate: Box[NodeSeq] =
    allParams.flatMap {
      case Loc.Template(f) => Some(f());
      case Loc.ValueTemplate(f) => Some(f(currentValue));
      case Loc.TemplateBox(f) => f()
      case Loc.ValueTemplateBox(f) => f(currentValue)
      case _ => None
    }.headOption

  /**
   * The template assocaited with this Loc, if any. Any Loc.Template
   * or Loc.ValueTemplate parameter will take precedence over a value returned
   * by the calcTemplate method.
   */
  def template: Box[NodeSeq] = paramTemplate or calcTemplate

  /**
   * The first Loc.Title in the param list.
   */
  lazy val paramTitle: Box[T => NodeSeq] =
    allParams.flatMap {
      case Loc.Title(f) => Some(f);
      case _ => None
    }.headOption

  /**
   * The title to be displayed for the value associated with this Loc.
   * Any Loc.Title parameter will take precedence over the
   * value returned by the linkText method.
   */
  def title(in: T): NodeSeq = paramTitle.map(_.apply(in)) openOr linkText(in)

  /**
   * The title of the location given the current value associated with this Loc.
   * If no current value is available, this will use the name of this Loc
   * as the title.
   */
  def title: NodeSeq = currentValue.map(title _) openOr Text(name)

  /**
   * The link text to be displayed for a value of type T
   */
  def linkText(in: T): NodeSeq = text.text(in)

  /**
   * The title of the location given the current value associated with this Loc
   */
  def linkText: Box[NodeSeq] = currentValue.map(linkText _)

  private var _menu: Menu = _

  private[sitemap] def menu_=(m: Menu): Unit = {
    _menu = m
    m.siteMap.addLoc(this)
  }

  def menu = _menu

  private def testAllParams(params: List[Loc.LocParam[T]], req: Req): Boolean = {
    params.forall {
      case Loc.Test(test) => test(req)
      case _ => true
    }
  }

  def doesMatch_?(req: Req): Boolean = {
    link.isDefinedAt(req) &&
    testAllParams(allParams, req) &&
    (
      currentValue.isDefined ||
      params.contains(Loc.MatchWithoutCurrentValue)
    )
  }

  def breadCrumbs: List[Loc[_]] = {
    if (_menu != null) {
      _menu.breadCrumbs ::: List(this)
    } else {
      List(this)
    }
  }

  def buildKidMenuItems(kids: Seq[Menu]): List[MenuItem] = {
    kids.toList.flatMap(_.loc.buildItem(Nil, false, false)) ::: supplementalKidMenuItems
  }

  def supplementalKidMenuItems: List[MenuItem] =
    for {
      p <- childValues
      l <- link.createLink(p).map(appendQueryParams(p))
    } yield MenuItem(
        text.text(p),
        l, Nil, false, false,
        allParams.flatMap {
          case v: Loc.LocInfo[_] => List(v())
          case _ => Nil
        }
      )

  def buildMenu: CompleteMenu = {
    CompleteMenu(_menu.buildUpperLines(_menu, _menu, buildKidMenuItems(_menu.kids)))
  }

  private[liftweb] def buildItem(kids: List[MenuItem], current: Boolean, path: Boolean): Box[MenuItem] =
    (calcHidden(kids), testAccess) match {
      case (false, Left(true)) => {
          for {
            p <- currentValue
            t <- link.createLink(p).map(appendQueryParams(p))
          } yield new MenuItem(
            text.text(p),
            t, kids, current, path,
            allParams.flatMap {
              case v: Loc.LocInfo[_] => List(v())
              case _ => Nil
            },
            placeHolder_?, this
          )
        }

      case _ => Empty
    }

  protected def calcHidden(kids: List[MenuItem]) = hidden || (hideIfNoKids_? && kids.isEmpty)

  private lazy val _hidden = allParams.contains(Loc.Hidden)

  def hidden = _hidden

  private lazy val groupSet: Set[String] =
    Set(allParams.flatMap{case s: Loc.LocGroup => s.group case _ => Nil} :_*)

  def inGroup_?(group: String): Boolean = groupSet.contains(group)

  def init(): Unit = {
    params.foreach(_ onCreate(this))
  }

}

trait ConvertableLoc[T] {
  self: Loc[T] =>

  /**
   * Converts the String to T that can then be sent to
   * the Loc in createLink
   */
  def convert(str: String): Box[T]
}


/**
 * The Loc companion object, complete with a nice constructor
 */
object Loc {
  type FailMsg = () => LiftResponse

  /**
   * Create a Loc (Location) instance
   *
   * @param name -- the name of the location.  This must be unique across your entire sitemap.
   * It's used to look up a menu item in order to create a link to the menu on a page.
   * @param link -- the Link to the page
   * @param text -- the text to display when the link is displayed
   * @param params -- access test, title calculation, etc.
   */
  def apply(name: String, link: Link[Unit], text: LinkText[Unit], params: LocParam[Unit]*): Loc[Unit] = UnitLoc(name, link, text, params.toList)
  def apply(name: String, link: Link[Unit], text: LinkText[Unit], params: List[LocParam[Unit]]): Loc[Unit] = UnitLoc(name, link, text, params)

  private final case class UnitLoc(
    override val name: String,
    override val link: Link[Unit],
    override val text: LinkText[Unit],
    override val params: List[LocParam[Unit]]
  ) extends Loc[Unit] {
    override def defaultValue: Box[Unit] = Full(())

    init()
  }

  case class DataLoc[T](
    override val name: String,
    override val link: Link[T],
    override val text: LinkText[T],
    override val defaultValue: Box[T],
    xparams: LocParam[T]*
  ) extends Loc[T] {
    override val params = xparams.toList

    init()
  }

  /**
   * Algebraic data type for parameters that modify handling of a Loc
   * in a SiteMap
   */
  trait LocParam[-T] {
    def onCreate(loc: Loc[_]): Unit = {
    }
  }

  /**
   * A type alias for LocParam instances that are applicable to any Loc
   */
  type AnyLocParam = LocParam[Any]

  /**
   * Indicates that the path denominated by Loc requires HTTP authentication
   * and only a user assigned to this role or to a role that is child-of this role
   * can access it.
   */
  case class HttpAuthProtected(role: (Req) => Box[Role]) extends AnyLocParam {

    override def onCreate(loc: Loc[_]): Unit = {
      LiftRules.httpAuthProtectedResource.append(
        new LiftRules.HttpAuthProtectedResourcePF() {
          def isDefinedAt(in: Req) = in.path.partPath == loc.link.uriList
          def apply(in: Req): Box[Role] = role(in)
        }
      )
    }
  }

  /**
   * Allows you to generate an early response for the location rather than
   * going through the whole Lift XHTML rendering pipeline
   */
  case class EarlyResponse(func: () => Box[LiftResponse]) extends AnyLocParam

  /**
   * Tests to see if the request actually matches the requirements for access to
   * the page.  For example, if a parameter is missing from the request, this
   * is a good way to restrict access to the page.
   */
  case class Test(test: Req => Boolean) extends AnyLocParam

  /**
   * If the test returns True, the page can be accessed, otherwise,
   * the result of FailMsg will be sent as a response to the browser.
   * If the Loc cannot be accessed, it will not be displayed in menus.
   *
   * @param test -- the function that tests access to the page
   * @param failMsg -- what to return the the browser (e.g., 304, etc.) if
   * the page is accessed.
   */
  case class If(test: () => Boolean, failMsg: FailMsg) extends AnyLocParam
  case class IfValue[T](test: Box[T] => Boolean, failMsg: FailMsg) extends LocParam[T]

  /**
   * MenuCssClass is used to add css to the Menu node.  The css allows for
   * replacing menu with an icon and other super-fun and helpful things.
   * cssClass is a StringFunc which can either be a String constant or
   * a Function that returns a String.  Thus, you can compute the
   * css based on the current state or you can have a constant.  Syntactically
   * you can use either:
   * 
   * MenuCssClass("foobar")
   * MenuCssClass(() => calculateCssForMyMenuItem())
   * 
*/ case class MenuCssClass(cssClass: StringFunc) extends AnyLocParam /** * Unless the test returns True, the page can be accessed, otherwise, * the result of FailMsg will be sent as a response to the browser. * If the Loc cannot be accessed, it will not be displayed in menus. * * @param test -- the function that tests access to the page * @param failMsg -- what to return the the browser (e.g., 304, etc.) if * the page is accessed. */ case class Unless(test: () => Boolean, failMsg: FailMsg) extends AnyLocParam case class UnlessValue[T](test: Box[T] => Boolean, failMsg: FailMsg) extends LocParam[T] /** * Allows extra access testing for a given menu location such that * you can generically return a response during access control * testing */ case class TestAccess(func: () => Box[LiftResponse]) extends AnyLocParam case class TestValueAccess[T](func: Box[T] => Box[LiftResponse]) extends LocParam[T] /** * Allows a user to specify a template based upon a function from the current * value encapsulated in the Loc */ case class Template(template: () => NodeSeq) extends AnyLocParam case class ValueTemplate[T](template: Box[T] => NodeSeq) extends LocParam[T] /** * Allows a user to specify a template based upon a function from the current * value encapsulated in the Loc. Allow the return of Box[NodeSeq] so that it's more * friendly to Templates. */ case class TemplateBox(template: () => Box[NodeSeq]) extends AnyLocParam case class ValueTemplateBox[T](template: Box[T] => Box[NodeSeq]) extends LocParam[T] /** * This LocParam may be used to specify a function that calculates a title for the page * based upon the current value encapsulated by this Loc. */ case class Title[T](title: T => NodeSeq) extends LocParam[T] /** * If the Loc is in a group (or groups) like "legal" "community" etc. * the groups can be specified and recalled at the top level */ case class LocGroup(group: String*) extends AnyLocParam /** * Calculate the value for the Loc. This is useful for parameterized * menus. It allows you to calculate the value of the Loc. */ case class CalcValue[T](func: () => Box[T]) extends LocParam[T] /** * The value of Loc */ case class Value[T](value: T) extends LocParam[T] /** * An extension point for adding arbitrary lazy values to a Loc. */ trait LocInfo[X] extends AnyLocParam { def apply(): Box[() => X] } /** * The common interface for `LocParam`s that provide snippet functions, * which can be aware of the `Loc` value. * @tparam A The type with which the `Loc` is parameterized. */ trait ValueSnippets[A] extends LocParam[A] { /** * Provides snippets for the `Loc`. * @return a `PartialFunction` that maps a snippet name, and an optional `Loc` value, in a `Tuple2`, * to a snippet function (`NodeSeq` => `NodeSeq`). */ def snippets: PartialFunction[(String, Box[A]), NodeSeq => NodeSeq] } object ValueSnippets { /** * Factory for general `ValueSnippets` instances by wrapping an existing `PartialFunction`. * @tparam A the type of the `Loc`'s value * @param pf a `PartialFunction` that maps a snippet name, and an optional `Loc` value, in a `Tuple2`, * to a snippet function (`NodeSeq` => `NodeSeq`). */ def apply[A](pf: PartialFunction[(String, Box[A]), NodeSeq => NodeSeq]): ValueSnippets[A] = new ValueSnippets[A] { def snippets = pf } } /** * A single snippet that's associated with a `Loc`, but is * not directly aware of the `Loc` value. * @see ValueSnippets * @param name the snippet name * @param _func the snippet function. Note that this is a call-by-name parameter; * that is, the function expression passed in will be evaluated each time * the function is invoked. */ class Snippet(val name: String, _func: => NodeSeq => NodeSeq) extends ValueSnippets[Any] with AnyLocParam { /** * The NodeSeq => NodeSeq function */ def func: NodeSeq => NodeSeq = _func def snippets = { case (`name`, _) => func } } object Snippet { /** * Build a Loc.Snippet instance out of a name and a DispatchSnippet (or StatefulSnippet, LiftScreen or Wizard). * The "render" method will be invoked on the Dispatch snippet */ def apply(name: String, snippet: => DispatchSnippet)(implicit disambiguate: DummyImplicit): Snippet = new Snippet(name, ns => snippet.dispatch("render")(ns)) // Issue #919 /** * Build a Loc.Snippet instance for a given name and a function. Note that the function is call-by-name * so that it will be created each time it's used. This is useful for CSS Selector Transforms */ def apply(name: String, func: => NodeSeq => NodeSeq): Snippet = new Snippet(name, func) def unapply(in: Snippet): Option[(String, NodeSeq => NodeSeq)] = Some(in.name -> in.func) } /** * Allows you to create a handler for many snippets that are associated with * a Loc, but agnostic to the `Loc`'s value. * @see ValueSnippets * @see ValueSnippets.apply */ trait LocSnippets extends PartialFunction[String, NodeSeq => NodeSeq] with ValueSnippets[Any] with AnyLocParam { def snippets = { case (s, _) if isDefinedAt(s) => this.apply(s) } } /** * A subclass of LocSnippets with a built in dispatch method (no need to * implement isDefinedAt or apply... just * def dispatch: PartialFunction[String, NodeSeq => NodeSeq]. * @see ValueSnippets * @see ValueSnippets.apply */ trait DispatchLocSnippets extends LocSnippets { def dispatch: PartialFunction[String, NodeSeq => NodeSeq] def isDefinedAt(n: String) = dispatch.isDefinedAt(n) def apply(n: String) = dispatch.apply(n) } /** * If this parameter is included, the item will not be visible in the menu, but * will still be accessable. */ case object Hidden extends AnyLocParam /** * If this parameter is included, the Loc will continue to execute even if * currentValue is not defined. * * By default, Lift will determine that a Loc does not match a given request * if its currentValue comes up Empty, and as a result will return an HTTP 404. * For situations where this is not the desired, "Not Found" behavior, you can * add the MatchWithoutCurrentValue LocParam to a Loc, then use the IfValue * LocParam to define what should happen when the currentValue is Empty. * * For example, given some class Thing, you could do the following to trigger * a redirect when a Thing with a particular ID isn't found. * * {{{ * Menu.param[Thing]("Thing", "Thing", Thing.find(_), _.id) >> * MatchWithoutCurrentValue >> * IfValue(_.isDefined, () => RedirectResponse("/page/to/redirect/to")) * }}} */ case object MatchWithoutCurrentValue extends AnyLocParam /** * If this is a submenu, use the parent Loc's params */ case class UseParentParams() extends AnyLocParam /** * Calculate additional query parameters to add as a query * string to the Loc */ case class QueryParameters(f: () => List[(String, String)]) extends AnyLocParam /** * Calculate additional query parameters to add as a query * string to the Loc */ case class LocQueryParameters[T](f: Box[T] => List[(String, String)]) extends LocParam[T] /** * If the Loc has no children, hide the Loc itself */ case object HideIfNoKids extends AnyLocParam /** * Is the Loc a stateless Loc... it will be served * in stateless mode */ case object Stateless extends AnyLocParam /** * The Loc does not represent a menu itself, but is the parent menu for * children (implies HideIfNoKids) */ case object PlaceHolder extends AnyLocParam /** * Extension point for user-defined LocParam instances. */ trait UserLocParam[-T] extends LocParam[T] /** * A function that calculates the statelessness of the Loc for the given request */ case class CalcStateless(f: () => Boolean) extends AnyLocParam /** * A function that calculates the statelessness of the Loc for the given request * with the parameterized type passed into the function */ case class CalcParamStateless[-T](f: Box[T] => Boolean) extends LocParam[T] /** * A function that can be used to calculate the link text from the current * value encapsulated by the Loc. */ case class LinkText[-T](text: T => NodeSeq) /** * The companion object to LinkText that contains some helpful implicit conversion */ object LinkText { implicit def nodeSeqToLinkText[T](in: => NodeSeq): LinkText[T] = LinkText[T](T => in) implicit def strToLinkText[T](in: => String): LinkText[T] = LinkText(T => Text(in)) } /** * This defines the Link to the Loc. * * @param uriList -- the URL to match * * @param matchHead_? -- false -- absolute match. true -- match anything * that begins with the same path. Useful for opening a set of directories * (for example, help pages) */ class Link[-T](val uriList: List[String], val matchHead_? : Boolean) extends PartialFunction[Req, Box[Boolean]] { def this(b: List[String]) = this(b, false) def isDefinedAt(req: Req): Boolean = { if (matchHead_?) req.path.partPath.take(uriList.length) == uriList else uriList == req.path.partPath } /** * Is the Loc external */ def external_? = false def apply(in: Req): Box[Boolean] = { if (isDefinedAt(in)) Full(true) else throw new MatchError("Failed for Link "+uriList) } /** * Override this method to modify the uriList with data from the Loc's value */ def pathList(value: T): List[String] = uriList /** * Creates a string representation of the path to the Loc. */ def createPath(value: T): String = { val path: List[String] = pathList(value).map(Helpers.urlEncode) if (matchHead_?) { path.mkString("/", "/", "/") } else if (SiteMap.rawIndex_? && path == List("index")) { "/" } else if (path.length > 1 && path.last == "index") { path.dropRight(1).mkString("/", "/", "/") } else { path.mkString("/", "/", "") } } /** * Returns the value created by createPath wrapped in a boxed scala.xml.Text element. * NOTE: This does not create a clickable HTML link on its own! */ def createLink(value: T): Box[NodeSeq] = Full(Text(createPath(value))) } object Link { def apply(urlLst: List[String], matchHead_? : Boolean, url: String) = { new Link[Unit](urlLst, matchHead_?) { override def createLink(value: Unit): Box[NodeSeq] = Full(Text(url)) } } implicit def strLstToLink(in: Seq[String]): Link[Unit] = new Link[Unit](in.toList) implicit def strPairToLink(in: (Seq[String], Boolean)): Link[Unit] = new Link[Unit](in._1.toList, in._2) } object ExtLink { def apply(url: String) = new Link[Unit](Nil, false) { override def createLink(value: Unit): Box[NodeSeq] = Full(Text(url)) /** * Is the Loc external */ override def external_? = true } } implicit def strToFailMsg(in: => String): FailMsg = () => { RedirectWithState( LiftRules.siteMapFailRedirectLocation.mkString("/", "/", ""), RedirectState(Empty, in -> NoticeType.Error) ) } implicit def strFuncToFailMsg(in: () => String): FailMsg = strToFailMsg(in()) implicit def redirectToFailMsg(in: => RedirectResponse): FailMsg = () => in } case class CompleteMenu(lines: Seq[MenuItem]) { lazy val breadCrumbs: Seq[MenuItem] = lines.flatMap(_.breadCrumbs) } case class MenuItem(text: NodeSeq, uri: NodeSeq, kids: Seq[MenuItem], current: Boolean, path: Boolean, info: List[Box[() => _]]) { private var _placeholder = false def placeholder_? = _placeholder private var _cssClass: Box[String] = Empty def cssClass: Box[String] = _cssClass def this(text: NodeSeq, uri: NodeSeq, kids: Seq[MenuItem], current: Boolean, path: Boolean, info: List[Box[() => _]], ph: Boolean) = { this(text, uri, kids, current, path, info) _placeholder = ph } def this(text: NodeSeq, uri: NodeSeq, kids: Seq[MenuItem], current: Boolean, path: Boolean, info: List[Box[() => _]], ph: Boolean, loc: Loc[_]) = { this(text, uri, kids, current, path, info) _placeholder = ph _cssClass = loc.cssClassForMenuItem } def breadCrumbs: Seq[MenuItem] = { if (!path) Nil else this :: kids.toList.flatMap(_.breadCrumbs) } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy