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

net.liftweb.builtin.snippet.Menu.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 builtin
package snippet

import http.{S, DispatchSnippet, LiftRules}
import http.js._
import sitemap._
import util._
import common._
import scala.xml._
import JsCmds._
import JE._
import Helpers._

/**
 * 

This built-in snippet can be used to render a menu representing your SiteMap. * There are three main snippet methods that you can use:

* *
    *
  • builder - Renders the entire SiteMap, optionally expanding all child menus
  • *
  • group - Renders the MenuItems corresponding to the specified group.
  • *
  • item - Renders the specific named MenuItem
  • *
* *

More detailed usage of each method is provided below

*/ object Menu extends DispatchSnippet { def dispatch: DispatchIt = { case "builder" => builder case "title" => title case "item" => item case "group" => group case "json" => jsonMenu } /** *

This snippet method renders a menu representing your SiteMap contents. The * menu is rendered as a set of nested unordered lists (<ul />). By * default, it only renders nested child menus for menus that match the current request path. * You can add the "expandAll" attribute to the snippet tag to force full expansion of * all child menus. Additionally, you can use the following attribute prefixes to further customize * the generated list and list item elements:

* *
    *
  • top - Adds the specified attribute to the top-level <ul> element that makes up the menu
  • *
  • ul - Adds the specified attribute to each <ul> element (top-level and nested children) that makes up the menu
  • *
  • li - Adds the specified attribute to each <li> element for the menu
  • *
  • li_item - Adds the specified attribute to the current page’s menu item
  • *
  • outer_tag - the tag for the outer XML element (ul by default)
  • *
  • inner_tag - the tag for the inner XML element (li by default)
  • *
  • li_path - Adds the specified attribute to the current page’s breadcrumb path. The * breadcrumb path is the set of menu items leading to this one.
  • *
  • linkToSelf - False by default, but available as 'true' to generate link to the current page
  • *
  • level - Controls the level of menus that will be output. "0" is the top-level menu, "1" is children of * the current menu item, and so on. Child menus will be expanded unless the "expand" attribute is set to
    false
    .
  • *
  • expand - Controls whether or not to expand child menus. Defaults to
    true
    .
  • *
* *

If you are using designer friendly invocation, you can access the namespaced attributes:
* <div class="lift:Menu?li_item:class=foo+bar">menu</div> *

* *

For a simple, default menu, simply add

* *
   *   <lift:Menu.builder />
   * 
* *

To your template. You can render the entire sitemap with

* *
   *    <lift:Menu.builder expandAll="true" />
   * 
* *

Customizing the elements is handled through the prefixed attributes described above. * For instance, you could make the current page menu item red:

* *
   *    <lift:Menu.builder li_item:style="color: red;" />
   * 
*/ def builder(info: NodeSeq): NodeSeq = { val outerTag: String = S.attr("outer_tag") openOr "ul" val innerTag: String = S.attr("inner_tag") openOr "li" val expandAll = (S.attr("expandAll") or S.attr("expandall")).isDefined val linkToSelf: Boolean = (S.attr("linkToSelf") or S.attr("linktoself")).map(Helpers.toBoolean) openOr false val expandAny: Boolean = S.attr("expand").map(Helpers.toBoolean) openOr true val level: Box[Int] = for (lvs <- S.attr("level"); i <- Helpers.asInt(lvs)) yield i val toRender: Seq[MenuItem] = (S.attr("item"), S.attr("group")) match { case (Full(item), _) => for{ sm <- LiftRules.siteMap.toList req <- S.request.toList loc <- sm.findLoc(item).toList item <- buildItemMenu(loc, req.location, expandAll) } yield item case (_, Full(group)) => for{ sm <- LiftRules.siteMap.toList loc <- sm.locForGroup(group) req <- S.request.toList item <- buildItemMenu(loc, req.location, expandAll) } yield item case _ => renderWhat(expandAll) } def ifExpandCurrent(f: => NodeSeq): NodeSeq = if (expandAny || expandAll) f else NodeSeq.Empty def ifExpandAll(f: => NodeSeq): NodeSeq = if (expandAll) f else NodeSeq.Empty toRender.toList match { case Nil if S.attr("group").isDefined => NodeSeq.Empty case Nil => Text("No Navigation Defined.") case xs => val liMap = S.prefixedAttrsToMap("li") val li = S.mapToAttrs(liMap) def buildANavItem(i: MenuItem) = { i match { // Per Loc.PlaceHolder, placeholder implies HideIfNoKids case m@MenuItem(text, uri, kids, _, _, _) if m.placeholder_? && kids.isEmpty => NodeSeq.Empty case m@MenuItem(text, uri, kids, _, _, _) if m.placeholder_? => Helpers.addCssClass(i.cssClass, Elem(null, innerTag, Null, TopScope, // Is a placeholder useful if we don't display the kids? I say no (DCB, 20101108) {text}{buildUlLine(kids)}) % (if (m.path) S.prefixedAttrsToMetaData("li_path", liMap) else Null) % (if (m.current) S.prefixedAttrsToMetaData("li_item", liMap) else Null)) case MenuItem(text, uri, kids, true, _, _) if linkToSelf => Helpers.addCssClass(i.cssClass, Elem(null, innerTag, Null, TopScope, {text}{ifExpandCurrent(buildUlLine(kids))}) % S.prefixedAttrsToMetaData("li_item", liMap)) case MenuItem(text, uri, kids, true, _, _) => Helpers.addCssClass(i.cssClass, Elem(null, innerTag, Null, TopScope, {text}{ifExpandCurrent(buildUlLine(kids))}) % S.prefixedAttrsToMetaData("li_item", liMap)) // Not current, but on the path, so we need to expand children to show the current one case MenuItem(text, uri, kids, _, true, _) => Helpers.addCssClass(i.cssClass, Elem(null, innerTag, Null, TopScope, {text}{buildUlLine(kids)}) % S.prefixedAttrsToMetaData("li_path", liMap)) case MenuItem(text, uri, kids, _, _, _) => Helpers.addCssClass(i.cssClass, Elem(null, innerTag, Null, TopScope, {text}{ifExpandAll(buildUlLine(kids))}) % li) } } def buildUlLine(in: Seq[MenuItem]): NodeSeq = if (in.isEmpty) { NodeSeq.Empty } else { if (outerTag.length > 0) { Elem(null, outerTag, Null, TopScope, {in.flatMap(buildANavItem)}) % S.prefixedAttrsToMetaData("ul") } else { in.flatMap(buildANavItem) } } val realMenuItems = level match { case Full(lvl) if lvl > 0 => def findKids(cur: Seq[MenuItem], depth: Int): Seq[MenuItem] = if (depth == 0) cur else findKids(cur.flatMap(mi => mi.kids), depth - 1) findKids(xs, lvl) case _ => xs } buildUlLine(realMenuItems) match { case top: Elem => top % S.prefixedAttrsToMetaData("top") case other => other } } } // This is used to build a MenuItem for a single Loc private def buildItemMenu [A] (loc : Loc[A], currLoc: Box[Loc[_]], expandAll : Boolean) : List[MenuItem] = { val kids : List[MenuItem] = if (expandAll) loc.buildKidMenuItems(loc.menu.kids) else Nil loc.buildItem(kids, currLoc == Full(loc), currLoc == Full(loc)).toList } private def renderWhat(expandAll: Boolean): Seq[MenuItem] = (if (expandAll) for {sm <- LiftRules.siteMap; req <- S.request} yield sm.buildMenu(req.location).lines else S.request.map(_.buildMenu.lines)) openOr Nil def jsonMenu(ignore: NodeSeq): NodeSeq = { val toRender = renderWhat(true) def buildItem(in: MenuItem): JsExp = in match { case MenuItem(text, uri, kids, current, path, _) => JsObj("text" -> text.toString, "uri" -> uri.toString, "children" -> buildItems(kids), "current" -> current, "cssClass" -> (in.cssClass openOr ""), "placeholder" -> in.placeholder_?, "path" -> path) } def buildItems(in: Seq[MenuItem]): JsExp = JsArray(in.map(buildItem) :_*) Script(JsCrVar(S.attr("var") openOr "lift_menu", JsObj("menu" -> buildItems(toRender)))) } /** *

Renders the title for the current request path (location). You can use this to * automatically set the title for your page based on your SiteMap:

* *
   * ⋮
   * <head>
   *   <title><lift:Menu.title /></title>
   * </head>
   * ⋮
   * 
*

HTML5 does not support tags inside the <title> tag, * so you must do: *

* *
   * <head>
   *   <title class="lift:Menu.title"e;>The page named %*% is being displayed</title>
   * </head>
   * 
*

* And Lift will substitute the title at the %*% marker if the marker exists, otherwise * append the title to the contents of the <title> tag. *

*/ def title(text: NodeSeq): NodeSeq = { val r = for (request <- S.request; loc <- request.location) yield loc.title text match { case TitleText(attrs, str) => { r.map { rt => { val rts = rt.text val idx = str.indexOf("%*%") val bodyStr = if (idx >= 0) { str.substring(0, idx) + rts + str.substring(idx + 3) } else { str +" "+rts } {bodyStr} % attrs } } openOr text } case _ => { r openOr Text("") } } } private object TitleText { def unapply(in: NodeSeq): Option[(MetaData, String)] = if (in.length == 1 && in(0).isInstanceOf[Elem]) { val e = in(0).asInstanceOf[Elem] if (e.prefix == null && e.label == "title") { if (e.child.length == 0) { Some(e.attributes -> "") } else if (e.child.length == 1 && e.child(0).isInstanceOf[Atom[_]]) { Some(e.attributes -> e.child.text) } else None } else None } else None } /** *

Renders a group of menu items. You specify a group using the LocGroup LocItem * case class on your Menu Loc:

* *
   * val menus =
   *   Menu(Loc("a",...,...,LocGroup("test"))) ::
   *   Menu(Loc("b",...,...,LocGroup("test"))) ::
   *   Menu(Loc("c",...,...,LocGroup("test"))) :: Nil
   * 
* *

You can then render with the group snippet:

* *
   * <lift:Menu.group group="test" />
   * 
* *

Each menu item is rendered as an anchor tag (<a />), and you can customize * the tag using attributes prefixed with "a":

* *
   * <lift:Menu.group group="test" a:class="menulink" />
   * 
* *

You can also specify your own template within the Menu.group snippet tag, as long as * you provide a <menu:bind /> element where the snippet can place each menu item:

* *
   * <ul>
   * <lift:Menu.group group="test" >
   *   <li><menu:bind /></li>
   * </lift:Menu.group>
   * 
* */ def group(template: NodeSeq): NodeSeq = { val toBind = if ((template \ "bind").filter(_.prefix == "menu").isEmpty) else template val attrs = S.prefixedAttrsToMetaData("a") for (group <- S.attr("group").toList; siteMap <- LiftRules.siteMap.toList; loc <- siteMap.locForGroup(group); link <- loc.createDefaultLink; linkText <- loc.linkText) yield { val a = Helpers.addCssClass(loc.cssClassForMenuItem, {linkText} % attrs) Group(bind("menu", toBind, "bind" -> a)) } } /** *

Renders a specific, named item, based on the name given in the Menu's Loc paramter:

* *
   * val menus =
   *   Menu(Loc("a",...,...,LocGroup("test"))) ::
   *   Menu(Loc("b",...,...,LocGroup("test"))) ::
   *   Menu(Loc("c",...,...,LocGroup("test"))) :: Nil
   * 
* *

You can then select the item using the name attribute:

* *
   * <lift:Menu.item name="b" />
   * 
* *

The menu item is rendered as an anchor tag (<a />). The text for the link * defaults to the named Menu's Loc.linkText, but you can specify your own link text * by providing contents to the tag:

* *
   * <lift:Menu.item name="b">This is a link</lift:Menu.item>
   * 
* *

Additionally you can customize * the tag using attributes prefixed with "a":

* *
   * <lift:Menu.item name="b" a:style="color: red;" />
   * 
* *

The param attribute may be used with Menu Locs that are * CovertableLoc to parameterize the link

* *

Normally, the Menu item is not shown on pages that match its Menu's Loc. You can * set the "donthide" attribute on the tag to force it to show text only (same text as normal, * but not in an anchor tag)

* * *

Alternatively, you can set the "linkToSelf" attribute to "true" to force a link. You * can specify your own link text with the tag's contents. Note that case is significant, so * make sure you specify "linkToSelf" and not "linktoself".

* */ def item(_text: NodeSeq): NodeSeq = { val donthide = S.attr("donthide").map(Helpers.toBoolean) openOr false val linkToSelf = (S.attr("linkToSelf") or S.attr("linktoself")).map(Helpers.toBoolean) openOr false val text = ("a" #> ((n: NodeSeq) => n match { case e: Elem => e.child case xs => xs })).apply(_text) for { name <- S.attr("name").toList } yield { type T = Q forSome {type Q} // Builds a link for the given loc def buildLink[T](loc : Loc[T]) = { Group(SiteMap.buildLink(name, text) match { case e : Elem => Helpers.addCssClass(loc.cssClassForMenuItem, e % S.prefixedAttrsToMetaData("a")) case x => x }) } (S.request.flatMap(_.location), S.attr("param"), SiteMap.findAndTestLoc(name)) match { case (_, Full(param), Full(loc: Loc[T] with ConvertableLoc[T])) => { (for { pv <- loc.convert(param) link <- loc.createLink(pv) } yield Helpers.addCssClass(loc.cssClassForMenuItem, % S.prefixedAttrsToMetaData("a"))) openOr Text("") } case (Full(loc), _, _) if loc.name == name => { (linkToSelf, donthide) match { case (true, _) => buildLink(loc) case (_, true) => { if (!text.isEmpty) { Group(text) } else { Group(loc.linkText openOr Text(loc.name)) } } case _ => Text("") } } case (Full(loc), _, _) => buildLink(loc) case _ => Text("") } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy