[mq]: twitter-bootstrap-forms-error+help.patch
authorTomas Zeman <tzeman@volny.cz>
Thu, 12 Apr 2012 13:36:45 +0200
changeset 51 523c5c5b84e5
parent 50 d67905a2c39d
child 52 f40c67ede33d
[mq]: twitter-bootstrap-forms-error+help.patch
src/main/scala/bootstrap/liftweb/Boot.scala
src/main/scala/fis/base/ui/BootstrapScreen.scala
src/main/scala/fis/base/ui/screen.scala
src/main/scala/net/tz/lift/model/FieldHelp.scala
src/main/webapp/css/base.css
src/main/webapp/templates-hidden/wizard-all.html
--- a/src/main/scala/bootstrap/liftweb/Boot.scala	Thu Apr 12 10:52:42 2012 +0200
+++ b/src/main/scala/bootstrap/liftweb/Boot.scala	Thu Apr 12 13:36:45 2012 +0200
@@ -46,6 +46,8 @@
 
     LiftRules.htmlProperties.default.set { r: Req =>
       Html5Properties(r.userAgent) }
+
+    ScreenRules.init()
   }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/base/ui/BootstrapScreen.scala	Thu Apr 12 13:36:45 2012 +0200
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * 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.
+ *
+ * BootstrapScreen is based on ScreenWizardRendered which is
+ *    Copyright 2010-2011 WorldWide Conferencing, LLC
+ *
+ */
+package fis.base.ui
+
+import net.liftweb.common._
+import net.liftweb.http._
+import net.liftweb.http.js._
+import net.liftweb.http.js.JsCmds._
+import net.liftweb.util._
+import net.liftweb.util.Helpers._
+import scala.xml._
+
+trait BootstrapScreen extends ScreenWizardRendered {
+
+  protected override def renderAll(currentScreenNumber: Box[NodeSeq],
+                          screenCount: Box[NodeSeq],
+                          wizardTop: Box[Elem],
+                          screenTop: Box[Elem],
+                          fields: List[ScreenFieldInfo],
+                          prev: Box[Elem],
+                          cancel: Box[Elem],
+                          next: Box[Elem],
+                          finish: Box[Elem],
+                          screenBottom: Box[Elem],
+                          wizardBottom: Box[Elem],
+                          nextId: (String, () => JsCmd),
+                          prevId: Box[(String, () => JsCmd)],
+                          cancelId: (String, () => JsCmd),
+                          theScreen: AbstractScreen,
+                          ajax_? : Boolean): NodeSeq = {
+
+    val notices: List[(NoticeType.Value, NodeSeq, Box[String])] = S.getAllNotices
+
+    def bindFieldLine(xhtml: NodeSeq): NodeSeq = {
+      fields.flatMap {
+        f =>
+          val theFormEarly = f.input
+          val curId = theFormEarly.flatMap(Helpers.findId) or
+            f.field.uniqueFieldId openOr Helpers.nextFuncName
+
+          val theForm = theFormEarly.map {
+            fe => {
+              val f = Helpers.deepEnsureUniqueId(fe)
+              val id = Helpers.findBox(f)(_.attribute("id").
+                map(_.text).
+                filter(_ == curId))
+              if (id.isEmpty) {
+                Helpers.ensureId(f, curId)
+              } else {
+                f
+              }
+            }
+          }
+
+          val myNotices = notices.filter(fi => fi._3.isDefined && fi._3 == curId)
+          val maxNotice = myNotices.map(_._1).sortWith { _.id > _.id }.headOption
+
+          def doLabel(in: NodeSeq): NodeSeq =
+            bind("wizard", in, AttrBindParam("for", curId, "for"), "bind" -%> f.text)
+
+          val line = bind("wizard", xhtml,
+            "label" -%> doLabel _,
+            "form" -%> theForm,
+            FuncBindParam("help", xml => {
+              f.help match {
+                case Full(hlp) => bind("wizard", xml, "bind" -%> hlp)
+                case _ => NodeSeq.Empty
+              }
+            }),
+            FuncBindParam("field_errors", xml => {
+              myNotices match {
+                case Nil => NodeSeq.Empty
+                case xs => bind("wizard", xml, "error" -%>
+                  (innerXml => xs.flatMap {
+                    case (noticeType, msg, _) =>
+                      val metaData: MetaData = noticeTypeToAttr(theScreen).map(_(noticeType)) openOr Null
+                      bind("wizard", innerXml, "bind" -%> msg).map {
+                        case e: Elem => e % metaData
+                        case x => x
+                      }
+                  }))
+              }
+            }))
+
+            maxNotice map { t =>
+              (".control-group [class+]" #> t.lowerCaseTitle)(line)
+            } getOrElse line
+      }
+    }
+
+    def url = S.uri
+
+    val snapshot = createSnapshot
+
+    def bindErrors(xhtml: NodeSeq): NodeSeq = notices.filter(_._3.isEmpty) match {
+      case Nil => NodeSeq.Empty
+      case xs =>
+        def doErrors(in: NodeSeq): NodeSeq = xs.flatMap {
+          case (noticeType, msg, _) =>
+            val metaData: MetaData = noticeTypeToAttr(theScreen).map(_(noticeType)) openOr Null
+            bind("wizard", in, "bind" -%>
+              (msg)).map {
+              case e: Elem => e % metaData
+              case x => x
+            }
+        }
+
+        bind("wizard", xhtml,
+          "item" -%> doErrors _)
+    }
+
+    def bindFields(xhtml: NodeSeq): NodeSeq = {
+      val ret =
+        (<form id={nextId._1} action={url}
+               method="post">
+          {S.formGroup(-1)(SHtml.hidden(() =>
+            snapshot.restore()))}{bind("wizard", xhtml,
+            "line" -%> bindFieldLine _)}{S.formGroup(4)(
+            SHtml.hidden(() => {
+              val res = nextId._2();
+              if (!ajax_?) {
+                val localSnapshot = createSnapshot
+                S.seeOther(url, () => {
+                  localSnapshot.restore
+                })
+              }
+              res
+            }))}
+        </form> %
+          theScreen.additionalAttributes) ++
+          prevId.toList.map {
+            case (id, func) =>
+              <form id={id} action={url} method="post">
+                {SHtml.hidden(() => {
+                snapshot.restore();
+                val res = func();
+                if (!ajax_?) {
+                  val localSnapshot = createSnapshot;
+                  S.seeOther(url, () => localSnapshot.restore)
+                }
+                res
+              })}
+              </form>
+          } ++
+          <form id={cancelId._1} action={url} method="post">
+            {SHtml.hidden(() => {
+            snapshot.restore();
+            val res = cancelId._2() // WizardRules.deregisterWizardSession(CurrentSession.is)
+            if (!ajax_?) {
+              S.seeOther(Referer.get)
+            }
+            res
+          })}
+          </form>
+
+      if (ajax_?) {
+        SHtml.makeFormsAjax(ret)
+      } else {
+        ret
+      }
+    }
+
+    def bindScreenInfo(xhtml: NodeSeq): NodeSeq = (currentScreenNumber, screenCount) match {
+      case (Full(num), Full(cnt)) =>
+        bind("wizard", xhtml, "screen_number" -%> num /*Text(CurrentScreen.is.map(s => (s.myScreenNum + 1).toString) openOr "")*/ ,
+          "total_screens" -%> cnt /*Text(screenCount.toString)*/)
+      case _ => NodeSeq.Empty
+    }
+
+    Helpers.bind("wizard", allTemplate,
+      "screen_info" -%> bindScreenInfo _,
+      FuncBindParam("wizard_top", xml => (wizardTop.map(top => bind("wizard", xml, "bind" -%> top)) openOr NodeSeq.Empty)),
+      FuncBindParam("screen_top", xml => (screenTop.map(top => bind("wizard", xml, "bind" -%> top)) openOr NodeSeq.Empty)),
+      FuncBindParam("wizard_bottom", xml => (wizardBottom.map(bottom => bind("wizard", xml, "bind" -%> bottom)) openOr NodeSeq.Empty)),
+      FuncBindParam("screen_bottom", xml => (screenBottom.map(bottom => bind("wizard", xml, "bind" -%> bottom)) openOr NodeSeq.Empty)),
+      "prev" -%> (prev openOr EntityRef("nbsp")),
+      "next" -%> ((next or finish) openOr EntityRef("nbsp")),
+      "cancel" -%> (cancel openOr EntityRef("nbsp")),
+      "errors" -%> bindErrors _,
+      FuncBindParam("fields", bindFields _))
+
+  }
+}
+
+
+// vim: set ts=2 sw=2 et:
--- a/src/main/scala/fis/base/ui/screen.scala	Thu Apr 12 10:52:42 2012 +0200
+++ b/src/main/scala/fis/base/ui/screen.scala	Thu Apr 12 13:36:45 2012 +0200
@@ -15,9 +15,9 @@
  */
 package fis.base.ui
 
-import net.liftweb.http.LiftScreen
+import net.liftweb.http._
 import net.tz.lift.model._
-import scala.xml.{Elem, Text, UnprefixedAttribute}
+import scala.xml.{Elem, MetaData, Null, Text, UnprefixedAttribute}
 
 trait SaveButton { self: LiftScreen =>
   override def finishButton: Elem =
@@ -38,9 +38,19 @@
     <button class="btn">{l10n("Cancel")}</button>
 }
 
-trait HorizontalScreen extends LiftScreen {
+trait HorizontalScreen extends LiftScreen with BootstrapScreen {
   override def additionalAttributes = new UnprefixedAttribute("class",
     Text("form-horizontal"), super.additionalAttributes)
 }
 
+object ScreenRules {
+  def init() {
+    LiftScreenRules.messageStyles.default.set(() => {
+      case NoticeType.Notice => new UnprefixedAttribute("class", "notice", Null)
+      case NoticeType.Warning => new UnprefixedAttribute("class", "warning", Null)
+      case NoticeType.Error => new UnprefixedAttribute("class", "error", Null)
+    }: PartialFunction[NoticeType.Value, MetaData])
+  }
+}
+
 // vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/net/tz/lift/model/FieldHelp.scala	Thu Apr 12 13:36:45 2012 +0200
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
+ *
+ * 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.tz.lift.model
+
+import net.liftweb.common._
+import net.liftweb.record._
+import net.liftweb.util.Helpers._
+
+/**
+ * Generic field help.
+ * Field help is taken from resource bundle.
+ */
+trait FieldHelp { self: OwnedField[_ <: Record[_]] =>
+  override def helpAsHtml = Box !! i18n("%s.%s.help".format(
+    camelifyMethod(owner.getClass.getSimpleName),
+    camelifyMethod(name)) filterNot {_ == '$'})
+}
+
+// vim: set ts=2 sw=2 et:
--- a/src/main/webapp/css/base.css	Thu Apr 12 10:52:42 2012 +0200
+++ b/src/main/webapp/css/base.css	Thu Apr 12 13:36:45 2012 +0200
@@ -56,6 +56,14 @@
   font-weight: bold;
 }
 
+.control-group label {
+  float: left;
+}
+
+p.inline {
+  margin: 0px 0px 0px 0px;
+}
+
 #secnav li {
   float: left;
   padding: 0px 10px 10px 0;
--- a/src/main/webapp/templates-hidden/wizard-all.html	Thu Apr 12 10:52:42 2012 +0200
+++ b/src/main/webapp/templates-hidden/wizard-all.html	Thu Apr 12 13:36:45 2012 +0200
@@ -10,26 +10,26 @@
   <fieldset>
     <legend></legend>
     <wizard:fields>
-      <div lift:bind="wizard:line" class="control-group">
-        <wizard:label>
-          <label wizard:for="" class="control-label"> <wizard:bind></wizard:bind> </label>
-        </wizard:label>
-        <div class="controls">
-          <wizard:form></wizard:form>
-          <wizard:help>
-            <span class="help-inline"> <wizard:bind></wizard:bind> </span>
-          </wizard:help>
-          <wizard:field_errors>
+      <wizard:line>
+        <div class="control-group">
+          <wizard:label>
+            <label wizard:for="" class="control-label"> <wizard:bind></wizard:bind> </label>
+          </wizard:label>
+          <div class="controls">
+            <wizard:form></wizard:form>
             <span class="help-inline">
-              <ul>
-                <wizard:error>
-                  <li><wizard:bind></wizard:bind></li>
-                </wizard:error> 
-              </ul>
+              <wizard:help>
+                  <p class="inline"><wizard:bind></wizard:bind></p>
+              </wizard:help>
+              <wizard:field_errors>
+                  <wizard:error>
+                    <p class="inline"><wizard:bind></wizard:bind></p>
+                  </wizard:error> 
+              </wizard:field_errors>
             </span>
-          </wizard:field_errors>
+          </div>
         </div>
-      </div>
+      </wizard:line>
     </wizard:fields>
     <div class="form-horizontal">
       <div class="form-actions">