Permalink
Browse files

Added a Bootstrap Modal wrapper with some variants like Modal, InfoDi…

…alog, ConfirmDialog, etc.
  • Loading branch information...
1 parent bda4aab commit 5b6d3e978ae09a07b8d5c15d307185590e866162 @tuhlmann tuhlmann committed Sep 30, 2012
@@ -0,0 +1,21 @@
+package net.liftmodules.widgets.bootstrap
+
+import net.liftweb.http.js.JsCmd
+import net.liftweb.http.js.JsCmds._
+import net.liftweb.http.js.JsMember
+import net.liftweb.http.js.HtmlFixer
+import scala.xml.NodeSeq
+
+object HtmlJsSeparator extends HtmlFixer {
+ case class Html(val html: String) extends JsCmd with JsMember {
+ val toJsCmd = "html(" + html + ")"
+ }
+ case class JS(val js: JsCmd) extends JsCmd with JsMember {
+ val toJsCmd = js.toJsCmd
+ }
+ def apply(content: NodeSeq): (Html, JS) =
+ fixHtmlAndJs("inline", content) match {
+ case (str, Nil) => (Html(str), JS(Noop))
+ case (str, cmds) => (Html(str), JS(cmds.reduceLeft(_ & _)))
+ }
+}
@@ -0,0 +1,188 @@
+package net.liftmodules.widgets.bootstrap
+
+import net.liftweb.common._
+import net.liftweb.util.Helpers._
+import scala.xml.NodeSeq
+import net.liftweb.http.js.JsCmd
+import net.liftweb.http.js.JsExp
+import net.liftweb.http.js.JsObj
+import net.liftweb.http.SHtml
+import net.liftweb.http.js.JsCmds._
+import net.liftweb.http.js.jquery.JqJE.JqId
+import net.liftweb.http.js.JE.Str
+import net.liftweb.http.js.JE.JsFunc
+import net.liftweb.http.js.jquery.JqJE.Jq
+import net.liftweb.http.js.JsObj
+import net.liftweb.http.js.JE._
+import scala.xml.Text
+import net.liftweb.http.js.jquery.JqJE.JqRemove
+import net.liftweb.http.SHtml._
+import net.liftweb.http.js.JE
+import net.liftweb.util.CssSel
+
+/**
+ * @author tuhlmann@agynamix.de
+ * A wrapper to easily construct a Bootstrap Modal dialog.
+ * Based on https://github.com/jgoday/liftweb-jquery-dialogs-sample
+ */
+trait BootstrapDialog extends JsCmd {
+
+ def onFull[T](valueBox: Box[T])(selFunc: T => CssSel): CssSel = {
+ (for (value <- valueBox) yield {
+ selFunc(value)
+ }) openOr "#notExistent" #> ""
+ }
+
+ lazy val _dialogId: String = "modal_%s".format(nextFuncName)
+ def dialogId = _dialogId
+
+ val defaultOptions: Map[String, JsExp] = Map(
+ "backdrop" -> "static"
+ )
+
+ val options: Seq[(String, JsExp)] = List()
+
+ def allOptions = defaultOptions ++ options.toMap
+
+ def clsOption: String = option("class")
+
+ def option(name: String): String = {
+ val re = allOptions.get(name) match {
+ case Some(JE.Str(s)) => s
+ case _ => ""
+ }
+ //println("CLASS %s: %s".format(name, re))
+ re
+ }
+
+ val body: NodeSeq = NodeSeq.Empty
+
+ protected def formContent = body
+
+ def cssClass: String = "modal fade " + clsOption
+
+ def toJsObj(map: Map[String, JsExp]): JsObj = {
+ map.foldLeft(JsObj())((jsobj, value) => jsobj +* JsObj(value))
+ }
+
+ def open: JsCmd = open(formContent)
+ def open(content: NodeSeq): JsCmd = {
+ val htmlAndJs = HtmlJsSeparator(content)
+ val js: JsCmd = (Jq("<div id='%s' class='%s' tabindex='-1' style='display:none'></div>".format(dialogId, cssClass)) ~>
+ JsFunc("appendTo", "body") ~> htmlAndJs._1) &
+ (JqId(dialogId) ~> JsFunc("modal", toJsObj(allOptions))) & htmlAndJs._2 &
+ Run("$('#%s').on('hidden', function () { $('#%s').remove(); })".format(dialogId, dialogId))
+ //println("JS: "+js.toJsCmd)
+ //println("FIXXED: "+htmlAndJs._2.toJsCmd)
+ js
+ }
+ lazy val toJsCmd = open.toJsCmd
+
+ def close: JsCmd = JqId(Str(dialogId)) ~> JsFunc("modal", "hide") ~> JqRemove()
+}
+
+trait StructuredBootstrapDialog extends BootstrapDialog {
+
+ val header: Box[NodeSeq] = Full(<h3>Dialog</h3>)
+ val footer: Box[NodeSeq] = Empty
+
+ //val removeDlgCmd = (JqId(Str(dialogId)) ~> JsFunc("modal", "hide") ~> JqRemove()).toJsCmd
+ val closeXTpl = <button name="close-x" type="button" class="close" aria-hidden="true" data-dismiss="modal">&times;</button>
+
+ protected def closeX = {
+ val cssSel = onFull(onCloseFunc){f => "@close-x [onclick]" #> ajaxInvoke(()=>f())}
+ cssSel(closeXTpl)
+ }
+
+ protected def onCloseFunc(): Box[()=>JsCmd] = Empty
+
+
+ override protected def formContent = {
+ <xml:group>
+ {header match {
+ case Full(headSeq) =>
+ <div class="modal-header">
+ {closeX}
+ {headSeq}
+ </div>
+ case _ => NodeSeq.Empty
+ }}
+ <div class="modal-body">
+ {body}
+ </div>
+ {footer match {
+ case Full(footSeq) =>
+ <div class="modal-footer">
+ {footSeq}
+ </div>
+ case _ => NodeSeq.Empty
+ }}
+ </xml:group>
+ }
+
+
+}
+
+trait DialogHelpers extends BootstrapDialog {
+
+ def genFooter(okTitle: String, okFunc: Box[()=>JsCmd], cancelTitle: String, cancelFunc: Box[()=>JsCmd]): NodeSeq = {
+ val okClass = "btn " + option("okClass")
+ val cancelClass = "btn " + option("cancelClass")
+ val footerTpl =
+ <xml:group>
+ <button name="cancel" class={cancelClass} data-dismiss="modal" aria-hidden="true" >{cancelTitle}</button>
+ <button name="ok" class={okClass} data-dismiss="modal" >{okTitle}</button>
+ </xml:group>
+
+ val cssSel =
+ onFull(cancelFunc){f => "@cancel [onclick]" #> ajaxInvoke(()=>f())} &
+ onFull(okFunc){f => "@ok [onclick]" #> ajaxInvoke(()=>f())}
+
+ cssSel(footerTpl)
+ }
+
+}
+
+/**
+ * Simplest and most flexible version to construct a dialog:
+ * Modal(NodeSeq Template, options to pass).
+ * For available options (applies to all other invocation variants, too), see Bootstrap's Modal options.
+ * In addition, these keys are supported:
+ * - class: classes to add to the top level dialog
+ * - okClass: classes to add to the ok button, the button which invokes the followup logic if the user accepts the dialog.
+ * - cancelClass: classes to add to the Cancel button
+ *
+ */
+object Modal {
+ def apply(body: NodeSeq, options: (String, JsExp)*) = new Modal(body, options: _*)
+}
+
+class Modal(override val body: NodeSeq, override val options: (String, JsExp)*) extends BootstrapDialog
+
+class InfoDialog(title: String, override val body: NodeSeq) extends StructuredBootstrapDialog {
+ override val header = Full(<h3>{title}</h3>)
+ override val footer = Full(<button class="btn" aria-hidden="true" data-dismiss="modal">Close</button>)
+}
+
+object ConfirmDialog {
+ def apply(title: String, body: NodeSeq, okFunc: ()=>JsCmd, options: (String, JsExp)*) =
+ new ConfirmDialog(Full(<h3>{title}</h3>), body, "Ok", Full(okFunc), "Cancel", Empty, options: _*)
+
+ def apply(title: String, body: NodeSeq, okFunc: ()=>JsCmd, cancelFunc: ()=>JsCmd, options: (String, JsExp)*) =
+ new ConfirmDialog(Full(<h3>{title}</h3>), body, "Ok", Full(okFunc), "Cancel", Full(cancelFunc), options: _*)
+}
+
+class ConfirmDialog(override val header: Box[NodeSeq], override val body: NodeSeq, okTitle: String, okFunc: Box[()=>JsCmd], cancelTitle: String, cancelFunc: Box[()=>JsCmd], override val options: (String, JsExp)*) extends StructuredBootstrapDialog with DialogHelpers {
+ override val footer = Full(genFooter(okTitle, okFunc, cancelTitle, cancelFunc))
+ override protected def onCloseFunc(): Box[()=>JsCmd] = cancelFunc
+}
+
+object ConfirmRemoveDialog {
+ def apply(title: String, msg: String, removeFunc: ()=>JsCmd, options: (String, JsExp)*) = {
+ val options2: Seq[(String, JsExp)] = List(("okClass" -> "btn-danger"))
+ new ConfirmDialog(Full(<h3>{title}</h3>), NodeSeq.Empty, "Remove", Full(removeFunc), "Cancel", Empty, (options2 ++ options): _*) {
+ override val body = <p class="lead">{msg}</p>
+ }
+ }
+}
+
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2007-2010 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 webapptest {
+package snippet {
+
+import _root_.scala.xml.NodeSeq
+import _root_.net.liftmodules.widgets.gravatar.Gravatar
+import net.liftmodules.widgets.bootstrap.Modal
+import net.liftweb.util.Helpers._
+import net.liftweb.http.SHtml
+import net.liftmodules.widgets.bootstrap.ConfirmRemoveDialog
+import net.liftweb.http.js.JsCmds.Alert
+
+/**
+ * @author tuhlmann@agynamix.de
+ * The following shows was to create a Bootstrap Modal Dialog. Please note that the Bootstrap CSS and the Modal
+ * JS file (part of Bootstrap) must be present to make this work.
+ * For other available dialog wrappers look at BootstrapDialog.scala
+ */
+class BootstrapDialogDemo {
+
+ def render = {
+ "#modal_dialog [onclick]" #> SHtml.ajaxInvoke(()=>Modal(<div>Some Template Here</div>, "keyboard" -> false, "class" -> "max"))
+
+ "#modal_remove [onclick]" #> SHtml.ajaxInvoke(()=>{
+ ConfirmRemoveDialog("Delete Item", "Really delete this item?",
+ ()=>{ Alert("Item deleted") })
+ })
+
+ }
+}
+
+}
+}
+
+
+
+
+
+
+

0 comments on commit 5b6d3e9

Please sign in to comment.